@dzhechkov/harness-core 0.3.134 → 0.3.136
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/.dz-manifest.json +46 -22
- package/README.md +2 -1
- package/dist/compounding.d.ts +109 -0
- package/dist/compounding.d.ts.map +1 -0
- package/dist/compounding.js +211 -0
- package/dist/compounding.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +24 -0
- package/dist/operations.js.map +1 -1
- package/dist/recall-usage.d.ts +21 -0
- package/dist/recall-usage.d.ts.map +1 -1
- package/dist/recall-usage.js +27 -3
- package/dist/recall-usage.js.map +1 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +21 -8
- package/dist/usage.js.map +1 -1
- package/package.json +5 -5
- package/sbom.json +81 -21
- package/src/compounding.ts +314 -0
- package/src/index.ts +5 -0
- package/src/operations.ts +24 -0
- package/src/recall-usage.ts +45 -3
- package/src/usage.ts +21 -5
package/src/recall-usage.ts
CHANGED
|
@@ -12,13 +12,31 @@
|
|
|
12
12
|
export const RECALL_USAGE_LOG_RELATIVE = '.dz/recall-usage.jsonl';
|
|
13
13
|
export const RECALL_USAGE_LOG_MAX_BYTES = 1_048_576;
|
|
14
14
|
export const RECALL_USAGE_COMPACT_TARGET_BYTES = Math.floor(RECALL_USAGE_LOG_MAX_BYTES * 0.75);
|
|
15
|
+
/** Newest query-bearing read rows survive compaction verbatim — they are the replay corpus. */
|
|
16
|
+
export const RECALL_USAGE_REPLAY_KEEP = 500;
|
|
15
17
|
|
|
16
18
|
export interface RecallUsageReadRecord {
|
|
17
19
|
readonly dzId: string;
|
|
18
20
|
readonly score: number;
|
|
19
21
|
readonly ts: string;
|
|
22
|
+
/**
|
|
23
|
+
* The PROMPT the lesson was injected into (truncated). Without it the log can say a lesson was
|
|
24
|
+
* used but never say FOR WHAT — which made cold-vs-warm replay unbuildable from 39 recorded
|
|
25
|
+
* events (MEASURED, compounding data inventory 2026-07-28). `.dz/` is git-ignored, so a truncated
|
|
26
|
+
* query stays on this machine.
|
|
27
|
+
*/
|
|
28
|
+
readonly query?: string;
|
|
29
|
+
/** The session/run the injection happened in — lets replay group events into runs. */
|
|
30
|
+
readonly runId?: string;
|
|
31
|
+
/** One id per PROMPT (the hook writes one row per injected hit — up to 3 per prompt). */
|
|
32
|
+
readonly eventId?: string;
|
|
33
|
+
/** True when the stored query is a PREFIX of the real prompt — not replayable. */
|
|
34
|
+
readonly queryTruncated?: boolean;
|
|
20
35
|
}
|
|
21
36
|
|
|
37
|
+
/** Query text is capped so a pasted wall of text cannot bloat the log. */
|
|
38
|
+
export const RECALL_USAGE_QUERY_MAX_CHARS = 200;
|
|
39
|
+
|
|
22
40
|
export interface RecallUsageAggregateRecord {
|
|
23
41
|
readonly kind: 'aggregate';
|
|
24
42
|
readonly dzId: string;
|
|
@@ -90,6 +108,10 @@ export function formatRecallUsageRecord(input: {
|
|
|
90
108
|
readonly dzId?: unknown;
|
|
91
109
|
readonly score?: unknown;
|
|
92
110
|
readonly ts?: unknown;
|
|
111
|
+
readonly query?: unknown;
|
|
112
|
+
readonly runId?: unknown;
|
|
113
|
+
readonly eventId?: unknown;
|
|
114
|
+
readonly queryTruncated?: unknown;
|
|
93
115
|
}): string | undefined {
|
|
94
116
|
const rec = normalizeReadRecord(input);
|
|
95
117
|
return rec === undefined ? undefined : `${JSON.stringify(rec)}\n`;
|
|
@@ -198,8 +220,16 @@ export function compactRecallUsageLog(
|
|
|
198
220
|
const maxBytes = validMax(opts.maxBytes ?? RECALL_USAGE_LOG_MAX_BYTES);
|
|
199
221
|
const targetBytes = validTarget(opts.targetBytes ?? Math.floor(maxBytes * 0.75), maxBytes);
|
|
200
222
|
const compactedAt = validTs(opts.compactedAt) ? opts.compactedAt : new Date(0).toISOString();
|
|
201
|
-
const
|
|
202
|
-
const
|
|
223
|
+
const parsed = parseRecallUsageLog(text).records;
|
|
224
|
+
const stats = aggregateRecallUsage(parsed);
|
|
225
|
+
// The query-bearing read rows are the ONLY inputs a cold-vs-warm replay has — aggregating them
|
|
226
|
+
// away silently reset the accruing corpus at the size threshold (Codex #4). Keep the NEWEST ones
|
|
227
|
+
// (bounded) verbatim alongside the aggregates.
|
|
228
|
+
const replayRows = parsed
|
|
229
|
+
.filter((r): r is RecallUsageReadRecord => !('kind' in r) && typeof (r as RecallUsageReadRecord).query === 'string')
|
|
230
|
+
.sort((a, b) => a.ts.localeCompare(b.ts))
|
|
231
|
+
.slice(-RECALL_USAGE_REPLAY_KEEP);
|
|
232
|
+
const lines = [...stats.map((s) => aggregateLine(s, compactedAt)), ...replayRows.map((r) => JSON.stringify(r))];
|
|
203
233
|
let out = joinLines(lines);
|
|
204
234
|
if (byteLength(out) <= maxBytes) return out;
|
|
205
235
|
|
|
@@ -232,7 +262,19 @@ function normalizeReadRecord(value: unknown): RecallUsageReadRecord | undefined
|
|
|
232
262
|
if (typeof dzId !== 'string' || dzId.trim() === '') return undefined;
|
|
233
263
|
if (typeof score !== 'number' || !Number.isFinite(score)) return undefined;
|
|
234
264
|
if (!validTs(ts)) return undefined;
|
|
235
|
-
|
|
265
|
+
const query = value['query'];
|
|
266
|
+
const runId = value['runId'];
|
|
267
|
+
return {
|
|
268
|
+
dzId: dzId.trim(),
|
|
269
|
+
score,
|
|
270
|
+
ts,
|
|
271
|
+
...(typeof query === 'string' && query.trim() !== ''
|
|
272
|
+
? { query: query.slice(0, RECALL_USAGE_QUERY_MAX_CHARS) }
|
|
273
|
+
: {}),
|
|
274
|
+
...(typeof runId === 'string' && runId.trim() !== '' ? { runId: runId.trim() } : {}),
|
|
275
|
+
...(typeof value['eventId'] === 'string' && (value['eventId'] as string).trim() !== '' ? { eventId: (value['eventId'] as string).trim() } : {}),
|
|
276
|
+
...(value['queryTruncated'] === true ? { queryTruncated: true } : {}),
|
|
277
|
+
};
|
|
236
278
|
}
|
|
237
279
|
|
|
238
280
|
function normalizeAggregateRecord(value: Record<string, unknown>): RecallUsageAggregateRecord | undefined {
|
package/src/usage.ts
CHANGED
|
@@ -321,7 +321,8 @@ function listTranscriptFiles(root: string): Array<{ path: string; mtimeMs: numbe
|
|
|
321
321
|
const projDir = join(root, d);
|
|
322
322
|
let files: string[];
|
|
323
323
|
try {
|
|
324
|
-
|
|
324
|
+
// lstat, not stat: a symlinked project directory would otherwise be walked (Codex #3).
|
|
325
|
+
const st = lstatSync(projDir);
|
|
325
326
|
if (!st.isDirectory()) continue;
|
|
326
327
|
files = readdirSync(projDir);
|
|
327
328
|
} catch {
|
|
@@ -334,10 +335,9 @@ function listTranscriptFiles(root: string): Array<{ path: string; mtimeMs: numbe
|
|
|
334
335
|
if (!f.endsWith('.jsonl')) {
|
|
335
336
|
const nested = join(projDir, f, 'subagents');
|
|
336
337
|
try {
|
|
337
|
-
if (!
|
|
338
|
+
if (!lstatSync(nested).isDirectory()) continue; // no symlinked session/subagent dirs
|
|
338
339
|
for (const sf of readdirSync(nested)) {
|
|
339
340
|
if (!sf.endsWith('.jsonl')) continue;
|
|
340
|
-
if (out.length >= MAX_TRANSCRIPT_FILES) break;
|
|
341
341
|
const sp = join(nested, sf);
|
|
342
342
|
const m = regularFileMtime(sp);
|
|
343
343
|
if (m !== null) out.push({ path: sp, mtimeMs: m });
|
|
@@ -347,12 +347,17 @@ function listTranscriptFiles(root: string): Array<{ path: string; mtimeMs: numbe
|
|
|
347
347
|
}
|
|
348
348
|
continue;
|
|
349
349
|
}
|
|
350
|
-
if (out.length >= MAX_TRANSCRIPT_FILES) break;
|
|
351
350
|
const p = join(projDir, f);
|
|
352
351
|
const mt = regularFileMtime(p);
|
|
353
352
|
if (mt !== null) out.push({ path: p, mtimeMs: mt });
|
|
354
353
|
}
|
|
355
354
|
}
|
|
355
|
+
// Cap by RECENCY, not by enumeration order: capping as we walked could discard the very files that
|
|
356
|
+
// hold current usage while keeping ancient ones (Codex #1).
|
|
357
|
+
if (out.length > MAX_TRANSCRIPT_FILES) {
|
|
358
|
+
out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
359
|
+
out.length = MAX_TRANSCRIPT_FILES;
|
|
360
|
+
}
|
|
356
361
|
return out;
|
|
357
362
|
}
|
|
358
363
|
|
|
@@ -426,7 +431,12 @@ function extractSamples(path: string, scanCutoff: number, into: Sample[], seen:
|
|
|
426
431
|
const reqId = typeof rec.requestId === 'string' ? rec.requestId : '';
|
|
427
432
|
// With no ids, fall back to a CONTENT key (timestamp + weighted total): the same record copied
|
|
428
433
|
// into both a main and a subagent transcript would otherwise be counted twice.
|
|
429
|
-
|
|
434
|
+
// Include the raw vector + model: `{input:50}` and `{output:10}` both weigh 50, so a
|
|
435
|
+
// total-only key silently merged distinct records (Codex #4).
|
|
436
|
+
const key =
|
|
437
|
+
id !== '' || reqId !== ''
|
|
438
|
+
? id + ':' + reqId
|
|
439
|
+
: `anon:${ts}:${n(usage.input_tokens)}:${n(usage.cache_creation_input_tokens)}:${n(usage.cache_read_input_tokens)}:${n(usage.output_tokens)}:${String(rec.message?.model ?? rec.model ?? '')}`;
|
|
430
440
|
if (seen.has(key)) continue;
|
|
431
441
|
seen.add(key);
|
|
432
442
|
into.push({ ts, tokens, key, model: normalizeClaudeUsageModel(rec.message?.model ?? rec.model) });
|
|
@@ -487,6 +497,12 @@ function configuredModelLimits(
|
|
|
487
497
|
* (all projects). `now` is injectable for tests.
|
|
488
498
|
*/
|
|
489
499
|
export function computeUsage(projectRoot: string, now?: number): UsageEstimate {
|
|
500
|
+
// never-throw contract: a non-finite or out-of-range clock reached `toISOString()` and raised
|
|
501
|
+
// RangeError. Clamp to the valid Date range instead of crashing the statusline (Codex #5).
|
|
502
|
+
const MAX_TIME = 8.64e15;
|
|
503
|
+
if (now !== undefined && (!isFinite(now) || Math.abs(now) > MAX_TIME)) {
|
|
504
|
+
now = Date.now();
|
|
505
|
+
}
|
|
490
506
|
const nowMs = typeof now === 'number' && isFinite(now) ? now : Date.now();
|
|
491
507
|
const limits = readUsageLimits(projectRoot);
|
|
492
508
|
const sessionBlockHours = limits.sessionBlockHours ?? DEFAULT_SESSION_BLOCK_HOURS;
|