@oh-my-pi/pi-coding-agent 14.5.14 → 14.6.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/CHANGELOG.md +49 -0
- package/package.json +7 -7
- package/src/autoresearch/command-resume.md +5 -8
- package/src/autoresearch/git.ts +41 -51
- package/src/autoresearch/helpers.ts +43 -359
- package/src/autoresearch/index.ts +281 -273
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +52 -193
- package/src/autoresearch/resume-message.md +2 -8
- package/src/autoresearch/state.ts +59 -166
- package/src/autoresearch/storage.ts +687 -0
- package/src/autoresearch/tools/init-experiment.ts +201 -290
- package/src/autoresearch/tools/log-experiment.ts +304 -517
- package/src/autoresearch/tools/run-experiment.ts +117 -296
- package/src/autoresearch/tools/update-notes.ts +116 -0
- package/src/autoresearch/types.ts +16 -66
- package/src/cli/list-models.ts +66 -0
- package/src/config/settings-schema.ts +1 -1
- package/src/config/settings.ts +20 -1
- package/src/cursor.ts +1 -1
- package/src/edit/index.ts +9 -31
- package/src/edit/line-hash.ts +70 -43
- package/src/edit/modes/hashline.lark +26 -0
- package/src/edit/modes/hashline.ts +898 -1099
- package/src/edit/modes/patch.ts +0 -7
- package/src/edit/modes/replace.ts +0 -4
- package/src/edit/renderer.ts +22 -20
- package/src/edit/streaming.ts +8 -28
- package/src/eval/eval.lark +24 -30
- package/src/eval/js/context-manager.ts +5 -162
- package/src/eval/js/prelude.txt +0 -12
- package/src/eval/parse.ts +129 -129
- package/src/eval/py/prelude.py +1 -219
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +2 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +18 -3
- package/src/modes/components/session-observer-overlay.ts +5 -2
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tree-selector.ts +4 -5
- package/src/modes/components/welcome.ts +11 -1
- package/src/modes/controllers/command-controller.ts +2 -6
- package/src/modes/controllers/event-controller.ts +7 -5
- package/src/modes/controllers/extension-ui-controller.ts +3 -15
- package/src/modes/controllers/input-controller.ts +0 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -7
- package/src/prompts/system/system-prompt.md +14 -38
- package/src/prompts/tools/ast-edit.md +8 -8
- package/src/prompts/tools/ast-grep.md +10 -10
- package/src/prompts/tools/eval.md +13 -31
- package/src/prompts/tools/find.md +2 -1
- package/src/prompts/tools/hashline.md +66 -57
- package/src/prompts/tools/search.md +2 -2
- package/src/session/agent-session.ts +1 -1
- package/src/session/session-manager.ts +17 -13
- package/src/tools/ast-edit.ts +141 -44
- package/src/tools/ast-grep.ts +112 -36
- package/src/tools/eval.ts +2 -53
- package/src/tools/find.ts +16 -15
- package/src/tools/gh-renderer.ts +184 -59
- package/src/tools/path-utils.ts +36 -196
- package/src/tools/search.ts +56 -35
- package/src/utils/edit-mode.ts +2 -11
- package/src/utils/file-display-mode.ts +1 -1
- package/src/utils/git.ts +59 -24
- package/src/utils/session-color.ts +0 -12
- package/src/utils/title-generator.ts +22 -38
- package/src/autoresearch/apply-contract-to-state.ts +0 -24
- package/src/autoresearch/contract.ts +0 -288
- package/src/edit/modes/atom.lark +0 -29
- package/src/edit/modes/atom.ts +0 -1773
- package/src/prompts/tools/atom.md +0 -150
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { parseCommandArgs } from "../utils/command-args";
|
|
5
|
-
import type {
|
|
6
|
-
ASIData,
|
|
7
|
-
ASIValue,
|
|
8
|
-
AutoresearchConfig,
|
|
9
|
-
MetricDirection,
|
|
10
|
-
NumericMetricMap,
|
|
11
|
-
PendingRunSummary,
|
|
12
|
-
} from "./types";
|
|
1
|
+
import type { ASIData, ASIValue, MetricDirection, NumericMetricMap } from "./types";
|
|
13
2
|
|
|
14
3
|
export const METRIC_LINE_PREFIX = "METRIC";
|
|
15
4
|
export const ASI_LINE_PREFIX = "ASI";
|
|
16
5
|
export const EXPERIMENT_MAX_LINES = 10;
|
|
17
6
|
export const EXPERIMENT_MAX_BYTES = 4 * 1024;
|
|
18
|
-
export const AUTORESEARCH_COMMITTABLE_FILES = [
|
|
19
|
-
"autoresearch.md",
|
|
20
|
-
"autoresearch.program.md",
|
|
21
|
-
"autoresearch.sh",
|
|
22
|
-
"autoresearch.checks.sh",
|
|
23
|
-
"autoresearch.ideas.md",
|
|
24
|
-
] as const;
|
|
25
|
-
export const AUTORESEARCH_LOCAL_STATE_FILES = ["autoresearch.jsonl"] as const;
|
|
26
|
-
export const AUTORESEARCH_LOCAL_STATE_DIRECTORIES = [".autoresearch"] as const;
|
|
27
7
|
|
|
28
8
|
const DENIED_KEY_NAMES = new Set(["__proto__", "constructor", "prototype"]);
|
|
29
9
|
|
|
@@ -120,51 +100,6 @@ export function formatElapsed(milliseconds: number): string {
|
|
|
120
100
|
return `${seconds}s`;
|
|
121
101
|
}
|
|
122
102
|
|
|
123
|
-
export function getAutoresearchRunDirectory(workDir: string, runNumber: number): string {
|
|
124
|
-
return path.join(workDir, ".autoresearch", "runs", String(runNumber).padStart(4, "0"));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function getNextAutoresearchRunNumber(workDir: string, lastRunNumber: number | null): number {
|
|
128
|
-
const runsDirectory = path.join(workDir, ".autoresearch", "runs");
|
|
129
|
-
let maxRunNumber = lastRunNumber ?? 0;
|
|
130
|
-
try {
|
|
131
|
-
for (const entry of fs.readdirSync(runsDirectory, { withFileTypes: true })) {
|
|
132
|
-
if (!entry.isDirectory()) continue;
|
|
133
|
-
const runNumber = Number.parseInt(entry.name, 10);
|
|
134
|
-
if (Number.isFinite(runNumber)) {
|
|
135
|
-
maxRunNumber = Math.max(maxRunNumber, runNumber);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} catch (error) {
|
|
139
|
-
if (!isEnoent(error)) {
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return maxRunNumber + 1;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function normalizeAutoresearchPath(relativePath: string): string {
|
|
147
|
-
const normalized = relativePath.replaceAll("\\", "/").trim();
|
|
148
|
-
if (normalized === "." || normalized === "./") return ".";
|
|
149
|
-
return normalized.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function isAutoresearchCommittableFile(relativePath: string): boolean {
|
|
153
|
-
const normalized = normalizeAutoresearchPath(relativePath);
|
|
154
|
-
return AUTORESEARCH_COMMITTABLE_FILES.some(candidate => candidate === normalized);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function isAutoresearchLocalStatePath(relativePath: string): boolean {
|
|
158
|
-
const normalized = normalizeAutoresearchPath(relativePath);
|
|
159
|
-
if (AUTORESEARCH_LOCAL_STATE_FILES.some(candidate => candidate === normalized)) {
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
return AUTORESEARCH_LOCAL_STATE_DIRECTORIES.some(candidate => {
|
|
163
|
-
const normalizedCandidate = normalizeAutoresearchPath(candidate);
|
|
164
|
-
return normalized === normalizedCandidate || normalized.startsWith(`${normalizedCandidate}/`);
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
103
|
export function killTree(pid: number, signal: NodeJS.Signals | number = "SIGTERM"): void {
|
|
169
104
|
try {
|
|
170
105
|
process.kill(-pid, signal);
|
|
@@ -177,47 +112,6 @@ export function killTree(pid: number, signal: NodeJS.Signals | number = "SIGTERM
|
|
|
177
112
|
}
|
|
178
113
|
}
|
|
179
114
|
|
|
180
|
-
export function isAutoresearchShCommand(command: string): boolean {
|
|
181
|
-
let normalized = command.trim();
|
|
182
|
-
normalized = normalized.replace(/^(?:\w+=\S*\s+)+/, "");
|
|
183
|
-
|
|
184
|
-
let previous = "";
|
|
185
|
-
while (previous !== normalized) {
|
|
186
|
-
previous = normalized;
|
|
187
|
-
normalized = normalized.replace(/^(?:env|time|nice|nohup)(?:\s+-\S+(?:\s+\d+)?)?\s+/, "");
|
|
188
|
-
}
|
|
189
|
-
if (/[;&|<>]/.test(normalized)) {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const tokens = parseCommandArgs(normalized);
|
|
194
|
-
if (tokens.length === 0) return false;
|
|
195
|
-
|
|
196
|
-
let index = 0;
|
|
197
|
-
if (tokens[index] === "bash" || tokens[index] === "sh") {
|
|
198
|
-
index += 1;
|
|
199
|
-
while (index < tokens.length && tokens[index]?.startsWith("-")) {
|
|
200
|
-
if (tokens[index]?.includes("c")) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
index += 1;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const scriptToken = tokens[index];
|
|
208
|
-
if (!scriptToken || !/^(?:\.\/|\/[\w/.-]*\/)?autoresearch\.sh$/.test(scriptToken)) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
for (const token of tokens.slice(index + 1)) {
|
|
213
|
-
if (token === "&&" || token === "||" || token === ";" || token === "|" || token === ">" || token === "<") {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
115
|
export function isBetter(current: number, best: number, direction: MetricDirection): boolean {
|
|
222
116
|
return direction === "lower" ? current < best : current > best;
|
|
223
117
|
}
|
|
@@ -231,287 +125,77 @@ export function inferMetricUnitFromName(name: string): string {
|
|
|
231
125
|
return "";
|
|
232
126
|
}
|
|
233
127
|
|
|
234
|
-
export
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
let entries: fs.Dirent[];
|
|
240
|
-
try {
|
|
241
|
-
entries = await fs.promises.readdir(runsDir, { withFileTypes: true });
|
|
242
|
-
} catch (error) {
|
|
243
|
-
if (isEnoent(error)) return null;
|
|
244
|
-
throw error;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const runDirectories = entries
|
|
248
|
-
.filter(entry => entry.isDirectory())
|
|
249
|
-
.map(entry => entry.name)
|
|
250
|
-
.sort((left, right) => right.localeCompare(left));
|
|
251
|
-
|
|
252
|
-
for (const directoryName of runDirectories) {
|
|
253
|
-
const runDirectory = path.join(runsDir, directoryName);
|
|
254
|
-
const runJsonPath = path.join(runDirectory, "run.json");
|
|
255
|
-
let parsed: unknown;
|
|
256
|
-
try {
|
|
257
|
-
parsed = await Bun.file(runJsonPath).json();
|
|
258
|
-
} catch (error) {
|
|
259
|
-
if (isEnoent(error)) continue;
|
|
260
|
-
throw error;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const pendingRun = parsePendingRunSummary(parsed, runDirectory, directoryName, loggedRunNumbers);
|
|
264
|
-
if (pendingRun) {
|
|
265
|
-
return pendingRun;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export async function abandonUnloggedAutoresearchRuns(
|
|
273
|
-
workDir: string,
|
|
274
|
-
loggedRunNumbers: ReadonlySet<number>,
|
|
275
|
-
): Promise<number> {
|
|
276
|
-
const runsDir = path.join(workDir, ".autoresearch", "runs");
|
|
277
|
-
let entries: fs.Dirent[];
|
|
278
|
-
try {
|
|
279
|
-
entries = await fs.promises.readdir(runsDir, { withFileTypes: true });
|
|
280
|
-
} catch (error) {
|
|
281
|
-
if (isEnoent(error)) return 0;
|
|
282
|
-
throw error;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let abandoned = 0;
|
|
286
|
-
const stamp = new Date().toISOString();
|
|
287
|
-
for (const entry of entries) {
|
|
288
|
-
if (!entry.isDirectory()) continue;
|
|
289
|
-
const directoryName = entry.name;
|
|
290
|
-
const runDirectory = path.join(runsDir, directoryName);
|
|
291
|
-
const runJsonPath = path.join(runDirectory, "run.json");
|
|
292
|
-
let parsed: unknown;
|
|
293
|
-
try {
|
|
294
|
-
parsed = await Bun.file(runJsonPath).json();
|
|
295
|
-
} catch (error) {
|
|
296
|
-
if (isEnoent(error)) continue;
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const pending = parsePendingRunSummary(parsed, runDirectory, directoryName, loggedRunNumbers);
|
|
301
|
-
if (!pending) continue;
|
|
302
|
-
|
|
303
|
-
const existing = typeof parsed === "object" && parsed !== null ? (parsed as Record<string, unknown>) : {};
|
|
304
|
-
await Bun.write(runJsonPath, JSON.stringify({ ...existing, abandonedAt: stamp }, null, 2));
|
|
305
|
-
abandoned += 1;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return abandoned;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export function readConfig(cwd: string): AutoresearchConfig {
|
|
312
|
-
const configPath = path.join(cwd, "autoresearch.config.json");
|
|
313
|
-
try {
|
|
314
|
-
const raw = fs.readFileSync(configPath, "utf8");
|
|
315
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
316
|
-
if (typeof parsed !== "object" || parsed === null) return {};
|
|
317
|
-
const candidate = parsed as { maxIterations?: unknown; workingDir?: unknown };
|
|
318
|
-
const config: AutoresearchConfig = {};
|
|
319
|
-
if (typeof candidate.maxIterations === "number" && Number.isFinite(candidate.maxIterations)) {
|
|
320
|
-
config.maxIterations = candidate.maxIterations;
|
|
321
|
-
}
|
|
322
|
-
if (typeof candidate.workingDir === "string" && candidate.workingDir.trim().length > 0) {
|
|
323
|
-
config.workingDir = candidate.workingDir;
|
|
324
|
-
}
|
|
325
|
-
return config;
|
|
326
|
-
} catch (error) {
|
|
327
|
-
if (isEnoent(error)) return {};
|
|
328
|
-
return {};
|
|
329
|
-
}
|
|
128
|
+
export function normalizePathSpec(value: string): string {
|
|
129
|
+
const trimmed = value.trim().replaceAll("\\", "/");
|
|
130
|
+
if (trimmed === "" || trimmed === "." || trimmed === "./") return ".";
|
|
131
|
+
const collapsed = trimmed.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
132
|
+
return collapsed.length === 0 ? "." : collapsed;
|
|
330
133
|
}
|
|
331
134
|
|
|
332
|
-
export function
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
135
|
+
export function pathMatchesSpec(pathValue: string, specValue: string): boolean {
|
|
136
|
+
const normalizedPath = normalizePathSpec(pathValue);
|
|
137
|
+
const normalizedSpec = normalizePathSpec(specValue);
|
|
138
|
+
if (normalizedSpec === ".") return true;
|
|
139
|
+
return normalizedPath === normalizedSpec || normalizedPath.startsWith(`${normalizedSpec}/`);
|
|
336
140
|
}
|
|
337
141
|
|
|
338
|
-
export function
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
const stat = fs.statSync(workDir);
|
|
348
|
-
if (!stat.isDirectory()) {
|
|
349
|
-
return `workingDir ${workDir} is not a directory.`;
|
|
350
|
-
}
|
|
351
|
-
return null;
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (isEnoent(error)) {
|
|
354
|
-
return `workingDir ${workDir} does not exist.`;
|
|
355
|
-
}
|
|
356
|
-
return `workingDir ${workDir} is unavailable.`;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function parsePendingRunSummary(
|
|
361
|
-
value: unknown,
|
|
362
|
-
runDirectory: string,
|
|
363
|
-
directoryName: string,
|
|
364
|
-
loggedRunNumbers: ReadonlySet<number>,
|
|
365
|
-
): PendingRunSummary | null {
|
|
366
|
-
if (typeof value !== "object" || value === null) return null;
|
|
367
|
-
const candidate = value as {
|
|
368
|
-
abandonedAt?: unknown;
|
|
369
|
-
checks?: { durationSeconds?: unknown; passed?: unknown; timedOut?: unknown };
|
|
370
|
-
completedAt?: unknown;
|
|
371
|
-
command?: unknown;
|
|
372
|
-
durationSeconds?: unknown;
|
|
373
|
-
exitCode?: unknown;
|
|
374
|
-
loggedAt?: unknown;
|
|
375
|
-
parsedAsi?: unknown;
|
|
376
|
-
parsedMetrics?: unknown;
|
|
377
|
-
parsedPrimary?: unknown;
|
|
378
|
-
preRunDirtyPaths?: unknown;
|
|
379
|
-
runNumber?: unknown;
|
|
380
|
-
status?: unknown;
|
|
381
|
-
timedOut?: unknown;
|
|
382
|
-
};
|
|
383
|
-
if (candidate.loggedAt !== undefined || candidate.status !== undefined) {
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
if (typeof candidate.abandonedAt === "string" && candidate.abandonedAt.trim().length > 0) {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const command = typeof candidate.command === "string" ? candidate.command : "";
|
|
391
|
-
const runNumber =
|
|
392
|
-
typeof candidate.runNumber === "number" && Number.isFinite(candidate.runNumber)
|
|
393
|
-
? candidate.runNumber
|
|
394
|
-
: parseInt(directoryName, 10);
|
|
395
|
-
if (!Number.isFinite(runNumber)) return null;
|
|
396
|
-
if (loggedRunNumbers.has(runNumber)) return null;
|
|
397
|
-
|
|
398
|
-
const hasCompletedMetadata =
|
|
399
|
-
typeof candidate.completedAt === "string" ||
|
|
400
|
-
candidate.exitCode !== undefined ||
|
|
401
|
-
candidate.timedOut !== undefined ||
|
|
402
|
-
candidate.durationSeconds !== undefined ||
|
|
403
|
-
candidate.checks !== undefined ||
|
|
404
|
-
candidate.parsedPrimary !== undefined ||
|
|
405
|
-
candidate.parsedMetrics !== undefined ||
|
|
406
|
-
candidate.parsedAsi !== undefined;
|
|
407
|
-
if (!hasCompletedMetadata) {
|
|
408
|
-
return null;
|
|
142
|
+
export function dedupeStrings(values: readonly string[]): string[] {
|
|
143
|
+
const out: string[] = [];
|
|
144
|
+
const seen = new Set<string>();
|
|
145
|
+
for (const value of values) {
|
|
146
|
+
const trimmed = value.trim();
|
|
147
|
+
if (trimmed.length === 0 || seen.has(trimmed)) continue;
|
|
148
|
+
seen.add(trimmed);
|
|
149
|
+
out.push(trimmed);
|
|
409
150
|
}
|
|
410
|
-
|
|
411
|
-
const checksPass =
|
|
412
|
-
typeof candidate.checks?.passed === "boolean"
|
|
413
|
-
? candidate.checks.passed
|
|
414
|
-
: typeof candidate.checks?.timedOut === "boolean" && candidate.checks.timedOut
|
|
415
|
-
? false
|
|
416
|
-
: null;
|
|
417
|
-
const exitCode =
|
|
418
|
-
typeof candidate.exitCode === "number" && Number.isFinite(candidate.exitCode) ? candidate.exitCode : null;
|
|
419
|
-
const timedOut = candidate.timedOut === true;
|
|
420
|
-
const durationSeconds =
|
|
421
|
-
typeof candidate.durationSeconds === "number" && Number.isFinite(candidate.durationSeconds)
|
|
422
|
-
? candidate.durationSeconds
|
|
423
|
-
: null;
|
|
424
|
-
const parsedPrimary =
|
|
425
|
-
typeof candidate.parsedPrimary === "number" && Number.isFinite(candidate.parsedPrimary)
|
|
426
|
-
? candidate.parsedPrimary
|
|
427
|
-
: null;
|
|
428
|
-
const parsedAsi = cloneAsiData(candidate.parsedAsi);
|
|
429
|
-
const parsedMetrics = cloneNumericMetricMap(candidate.parsedMetrics);
|
|
430
|
-
const checksDurationSeconds =
|
|
431
|
-
typeof candidate.checks?.durationSeconds === "number" && Number.isFinite(candidate.checks.durationSeconds)
|
|
432
|
-
? candidate.checks.durationSeconds
|
|
433
|
-
: null;
|
|
434
|
-
const checksTimedOut = candidate.checks?.timedOut === true;
|
|
435
|
-
|
|
436
|
-
const preRunDirtyPaths = Array.isArray(candidate.preRunDirtyPaths)
|
|
437
|
-
? candidate.preRunDirtyPaths.filter((item): item is string => typeof item === "string")
|
|
438
|
-
: [];
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
checksDurationSeconds,
|
|
442
|
-
checksPass,
|
|
443
|
-
checksTimedOut,
|
|
444
|
-
command,
|
|
445
|
-
durationSeconds,
|
|
446
|
-
parsedAsi,
|
|
447
|
-
parsedMetrics,
|
|
448
|
-
parsedPrimary,
|
|
449
|
-
passed: exitCode === 0 && !timedOut && checksPass !== false,
|
|
450
|
-
preRunDirtyPaths,
|
|
451
|
-
runDirectory,
|
|
452
|
-
runNumber,
|
|
453
|
-
};
|
|
151
|
+
return out;
|
|
454
152
|
}
|
|
455
153
|
|
|
456
|
-
function
|
|
457
|
-
if (
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
for (const [key, entryValue] of Object.entries(metrics)) {
|
|
154
|
+
export function ensureNumericMetricMap(value: NumericMetricMap | undefined): NumericMetricMap {
|
|
155
|
+
if (!value) return {};
|
|
156
|
+
const out: NumericMetricMap = {};
|
|
157
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
461
158
|
if (DENIED_KEY_NAMES.has(key)) continue;
|
|
462
159
|
if (typeof entryValue === "number" && Number.isFinite(entryValue)) {
|
|
463
|
-
|
|
160
|
+
out[key] = entryValue;
|
|
464
161
|
}
|
|
465
162
|
}
|
|
466
|
-
return
|
|
163
|
+
return out;
|
|
467
164
|
}
|
|
468
165
|
|
|
469
|
-
function
|
|
470
|
-
if (
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
for (const [key, entryValue] of Object.entries(candidate)) {
|
|
166
|
+
export function sanitizeAsi(value: { [key: string]: unknown } | undefined): ASIData | undefined {
|
|
167
|
+
if (!value) return undefined;
|
|
168
|
+
const result: ASIData = {};
|
|
169
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
474
170
|
if (DENIED_KEY_NAMES.has(key)) continue;
|
|
475
|
-
const sanitized =
|
|
171
|
+
const sanitized = sanitizeAsiValue(entryValue);
|
|
476
172
|
if (sanitized !== undefined) {
|
|
477
|
-
|
|
173
|
+
result[key] = sanitized;
|
|
478
174
|
}
|
|
479
175
|
}
|
|
480
|
-
return Object.keys(
|
|
176
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
481
177
|
}
|
|
482
178
|
|
|
483
|
-
function
|
|
179
|
+
function sanitizeAsiValue(value: unknown): ASIValue | undefined {
|
|
484
180
|
if (value === null) return null;
|
|
485
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
486
|
-
return value;
|
|
487
|
-
}
|
|
181
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
488
182
|
if (Array.isArray(value)) {
|
|
489
183
|
const items = value
|
|
490
|
-
.map(
|
|
491
|
-
.filter((
|
|
184
|
+
.map(item => sanitizeAsiValue(item))
|
|
185
|
+
.filter((item): item is NonNullable<typeof item> => item !== undefined);
|
|
492
186
|
return items;
|
|
493
187
|
}
|
|
494
188
|
if (typeof value === "object") {
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
for (const [key, entryValue] of Object.entries(
|
|
189
|
+
const objectValue = value as { [key: string]: unknown };
|
|
190
|
+
const result: ASIData = {};
|
|
191
|
+
for (const [key, entryValue] of Object.entries(objectValue)) {
|
|
498
192
|
if (DENIED_KEY_NAMES.has(key)) continue;
|
|
499
|
-
const sanitized =
|
|
193
|
+
const sanitized = sanitizeAsiValue(entryValue);
|
|
500
194
|
if (sanitized !== undefined) {
|
|
501
|
-
|
|
195
|
+
result[key] = sanitized;
|
|
502
196
|
}
|
|
503
197
|
}
|
|
504
|
-
return
|
|
198
|
+
return result;
|
|
505
199
|
}
|
|
506
200
|
return undefined;
|
|
507
201
|
}
|
|
508
|
-
|
|
509
|
-
export function collectLoggedRunNumbers(results: readonly { runNumber: number | null }[]): Set<number> {
|
|
510
|
-
const runNumbers = new Set<number>();
|
|
511
|
-
for (const result of results) {
|
|
512
|
-
if (result.runNumber !== null) {
|
|
513
|
-
runNumbers.add(result.runNumber);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return runNumbers;
|
|
517
|
-
}
|