@f5xc-salesdemos/xcsh 18.53.1 → 18.55.0

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 CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  ## [18.53.0] - 2026-05-09
6
6
 
7
+ ### Changed
8
+
9
+ - Autoresearch subsystem code quality: -513 lines (18.8% reduction), ~13% faster type checking. Un-exported internal symbols, relocated types, consolidated duplicate patterns, replaced manual deep copies with `structuredClone`, replaced `while(exec)` with `matchAll`, compressed control flow, extracted shared interfaces ([#734](https://github.com/f5xc-salesdemos/xcsh/pull/734))
10
+ ## [18.53.0] - 2026-05-09
11
+
7
12
  ### Fixed
8
13
 
9
14
  - Replaced `xcsh --version` recommendation in `renderAboutDoc()` with authoritative intrinsic version guidance — the previous guidance misdirected to the installed binary, not the running session ([#722](https://github.com/f5xc-salesdemos/xcsh/pull/722))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.53.1",
4
+ "version": "18.55.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5xc-salesdemos/xcsh",
7
7
  "author": "Can Boluk",
@@ -48,12 +48,12 @@
48
48
  "dependencies": {
49
49
  "@agentclientprotocol/sdk": "0.16.1",
50
50
  "@mozilla/readability": "^0.6",
51
- "@f5xc-salesdemos/xcsh-stats": "18.53.1",
52
- "@f5xc-salesdemos/pi-agent-core": "18.53.1",
53
- "@f5xc-salesdemos/pi-ai": "18.53.1",
54
- "@f5xc-salesdemos/pi-natives": "18.53.1",
55
- "@f5xc-salesdemos/pi-tui": "18.53.1",
56
- "@f5xc-salesdemos/pi-utils": "18.53.1",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.55.0",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.55.0",
53
+ "@f5xc-salesdemos/pi-ai": "18.55.0",
54
+ "@f5xc-salesdemos/pi-natives": "18.55.0",
55
+ "@f5xc-salesdemos/pi-tui": "18.55.0",
56
+ "@f5xc-salesdemos/pi-utils": "18.55.0",
57
57
  "@sinclair/typebox": "^0.34",
58
58
  "@xterm/headless": "^6.0",
59
59
  "ajv": "^8.18",
@@ -1,24 +1 @@
1
- import { inferMetricUnitFromName } from "./helpers";
2
- import type { AutoresearchContract, ExperimentState } from "./types";
3
-
4
- /**
5
- * Updates session fields from a validated `autoresearch.md` parse (same fields as `init_experiment`).
6
- * Does not touch `name`, `currentSegment`, `results`, `bestMetric`, `confidence`, or `maxExperiments`.
7
- */
8
- export function applyAutoresearchContractToExperimentState(
9
- contract: AutoresearchContract,
10
- state: ExperimentState,
11
- ): void {
12
- const benchmarkContract = contract.benchmark;
13
- state.metricName = benchmarkContract.primaryMetric ?? state.metricName;
14
- state.metricUnit = benchmarkContract.metricUnit;
15
- state.bestDirection = benchmarkContract.direction ?? "lower";
16
- state.secondaryMetrics = benchmarkContract.secondaryMetrics.map(name => ({
17
- name,
18
- unit: inferMetricUnitFromName(name),
19
- }));
20
- state.benchmarkCommand = benchmarkContract.command?.trim() ?? state.benchmarkCommand;
21
- state.scopePaths = [...contract.scopePaths];
22
- state.offLimits = [...contract.offLimits];
23
- state.constraints = [...contract.constraints];
24
- }
1
+ export { applyAutoresearchContractToExperimentState } from "./contract";
@@ -1,33 +1,25 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { AutoresearchBenchmarkContract, AutoresearchContract, MetricDirection } from "./types";
4
-
5
- export interface AutoresearchContractLoadResult {
6
- contract: AutoresearchContract;
7
- errors: string[];
8
- path: string;
9
- }
10
-
11
- export interface AutoresearchScriptSnapshot {
12
- benchmarkScript: string;
13
- benchmarkScriptPath: string;
14
- checksScript: string | null;
15
- checksScriptPath: string;
16
- errors: string[];
17
- }
3
+ import { inferMetricUnitFromName } from "./helpers";
4
+ import type { AutoresearchContract, ExperimentState, MetricDirection } from "./types";
18
5
 
19
6
  const HEADING_REGEX = /^##\s+(.+?)\s*$/;
20
7
  const LIST_ITEM_REGEX = /^\s*[-*]\s+(.*)$/;
21
8
  const KEY_VALUE_REGEX = /^\s*[-*]\s+([^:]+):\s*(.*)$/;
22
9
 
23
- export function readAutoresearchContract(workDir: string): AutoresearchContractLoadResult {
10
+ export function readAutoresearchContract(workDir: string) {
24
11
  const contractPath = path.join(workDir, "autoresearch.md");
25
12
  let content = "";
26
13
  try {
27
14
  content = fs.readFileSync(contractPath, "utf8");
28
15
  } catch {
29
16
  return {
30
- contract: createEmptyAutoresearchContract(),
17
+ contract: {
18
+ benchmark: { command: null, primaryMetric: null, metricUnit: "", direction: null, secondaryMetrics: [] },
19
+ scopePaths: [],
20
+ offLimits: [],
21
+ constraints: [],
22
+ },
31
23
  errors: [`${contractPath} does not exist. Create it before initializing autoresearch.`],
32
24
  path: contractPath,
33
25
  };
@@ -48,34 +40,24 @@ export function parseAutoresearchContract(markdown: string): AutoresearchContrac
48
40
  };
49
41
  }
50
42
 
51
- export function validateAutoresearchContract(contract: AutoresearchContract): string[] {
43
+ function validateAutoresearchContract(contract: AutoresearchContract): string[] {
52
44
  const errors: string[] = [];
53
- if (!contract.benchmark.command) {
54
- errors.push("Benchmark.command is required in autoresearch.md.");
55
- }
56
- if (!contract.benchmark.primaryMetric) {
57
- errors.push("Benchmark.primary metric is required in autoresearch.md.");
58
- }
59
- if (!contract.benchmark.direction) {
45
+ if (!contract.benchmark.command) errors.push("Benchmark.command is required in autoresearch.md.");
46
+ if (!contract.benchmark.primaryMetric) errors.push("Benchmark.primary metric is required in autoresearch.md.");
47
+ if (!contract.benchmark.direction)
60
48
  errors.push("Benchmark.direction must be `lower` or `higher` in autoresearch.md.");
61
- }
62
- if (contract.scopePaths.length === 0) {
49
+ if (contract.scopePaths.length === 0)
63
50
  errors.push("Files in Scope must contain at least one path in autoresearch.md.");
51
+ for (const p of contract.scopePaths) {
52
+ if (isUnsafeContractPathSpec(p)) errors.push(`Files in Scope contains an invalid path: ${p}`);
64
53
  }
65
- for (const scopePath of contract.scopePaths) {
66
- if (isUnsafeContractPathSpec(scopePath)) {
67
- errors.push(`Files in Scope contains an invalid path: ${scopePath}`);
68
- }
69
- }
70
- for (const offLimitsPath of contract.offLimits) {
71
- if (isUnsafeContractPathSpec(offLimitsPath)) {
72
- errors.push(`Off Limits contains an invalid path: ${offLimitsPath}`);
73
- }
54
+ for (const p of contract.offLimits) {
55
+ if (isUnsafeContractPathSpec(p)) errors.push(`Off Limits contains an invalid path: ${p}`);
74
56
  }
75
57
  return errors;
76
58
  }
77
59
 
78
- export function loadAutoresearchScriptSnapshot(workDir: string): AutoresearchScriptSnapshot {
60
+ export function loadAutoresearchScriptSnapshot(workDir: string) {
79
61
  const benchmarkScriptPath = path.join(workDir, "autoresearch.sh");
80
62
  const checksScriptPath = path.join(workDir, "autoresearch.checks.sh");
81
63
  const errors: string[] = [];
@@ -104,16 +86,7 @@ export function loadAutoresearchScriptSnapshot(workDir: string): AutoresearchScr
104
86
  }
105
87
 
106
88
  export function normalizeAutoresearchList(values: readonly string[]): string[] {
107
- const normalized: string[] = [];
108
- const seen = new Set<string>();
109
- for (const value of values) {
110
- const trimmed = value.trim();
111
- if (trimmed.length === 0) continue;
112
- if (seen.has(trimmed)) continue;
113
- seen.add(trimmed);
114
- normalized.push(trimmed);
115
- }
116
- return normalized;
89
+ return [...new Set(values.map(v => v.trim()).filter(Boolean))];
117
90
  }
118
91
 
119
92
  export function normalizeContractPathSpec(value: string): string {
@@ -130,34 +103,16 @@ export function pathMatchesContractPath(pathValue: string, specValue: string): b
130
103
  }
131
104
 
132
105
  export function contractListsEqual(left: readonly string[], right: readonly string[]): boolean {
133
- const normalizedLeft = normalizeAutoresearchList(left);
134
- const normalizedRight = normalizeAutoresearchList(right);
135
- if (normalizedLeft.length !== normalizedRight.length) return false;
136
- return normalizedLeft.every((value, index) => value === normalizedRight[index]);
106
+ return arraysEqual(normalizeAutoresearchList(left), normalizeAutoresearchList(right));
137
107
  }
138
108
 
139
109
  export function contractPathListsEqual(left: readonly string[], right: readonly string[]): boolean {
140
- const normalizedLeft = normalizeContractPathList(left);
141
- const normalizedRight = normalizeContractPathList(right);
142
- if (normalizedLeft.length !== normalizedRight.length) return false;
143
- return normalizedLeft.every((value, index) => value === normalizedRight[index]);
110
+ return arraysEqual(normalizeContractPathList(left), normalizeContractPathList(right));
144
111
  }
145
112
 
146
- function createEmptyAutoresearchContract(): AutoresearchContract {
147
- return {
148
- benchmark: {
149
- command: null,
150
- primaryMetric: null,
151
- metricUnit: "",
152
- direction: null,
153
- secondaryMetrics: [],
154
- },
155
- scopePaths: [],
156
- offLimits: [],
157
- constraints: [],
158
- };
113
+ function arraysEqual(a: string[], b: string[]): boolean {
114
+ return a.length === b.length && a.every((v, i) => v === b[i]);
159
115
  }
160
-
161
116
  function normalizeContractPathList(values: readonly string[]): string[] {
162
117
  return normalizeAutoresearchList(values.map(normalizeContractPathSpec)).sort((left, right) =>
163
118
  left.localeCompare(right),
@@ -166,39 +121,30 @@ function normalizeContractPathList(values: readonly string[]): string[] {
166
121
 
167
122
  function extractSections(markdown: string): Map<string, string> {
168
123
  const sections = new Map<string, string>();
169
- const lines = markdown.split("\n");
170
- let currentHeading: string | null = null;
171
- let currentLines: string[] = [];
172
-
173
- for (const line of lines) {
174
- const headingMatch = line.match(HEADING_REGEX);
175
- if (headingMatch) {
176
- if (currentHeading) {
177
- sections.set(currentHeading, currentLines.join("\n").trim());
178
- }
179
- currentHeading = headingMatch[1]?.trim().toLowerCase() ?? null;
180
- currentLines = [];
181
- continue;
182
- }
183
- if (currentHeading) {
184
- currentLines.push(line);
124
+ let heading: string | null = null;
125
+ let content: string[] = [];
126
+ for (const line of markdown.split("\n")) {
127
+ const match = line.match(HEADING_REGEX);
128
+ if (match) {
129
+ if (heading) sections.set(heading, content.join("\n").trim());
130
+ heading = match[1]?.trim().toLowerCase() ?? null;
131
+ content = [];
132
+ } else if (heading) {
133
+ content.push(line);
185
134
  }
186
135
  }
187
-
188
- if (currentHeading) {
189
- sections.set(currentHeading, currentLines.join("\n").trim());
190
- }
136
+ if (heading) sections.set(heading, content.join("\n").trim());
191
137
  return sections;
192
138
  }
193
139
 
194
- function parseBenchmarkSection(section: string): AutoresearchBenchmarkContract {
140
+ function parseBenchmarkSection(section: string): AutoresearchContract["benchmark"] {
195
141
  const entries = new Map<string, string>();
196
142
  const lines = section.split("\n");
197
143
  for (let index = 0; index < lines.length; index += 1) {
198
144
  const rawLine = lines[index] ?? "";
199
145
  const match = rawLine.match(KEY_VALUE_REGEX);
200
146
  if (!match) continue;
201
- const key = normalizeKey(match[1] ?? "");
147
+ const key = (match[1] ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
202
148
  let value = (match[2] ?? "").trim();
203
149
  if (key === "secondarymetrics") {
204
150
  const nestedItems: string[] = [];
@@ -206,27 +152,33 @@ function parseBenchmarkSection(section: string): AutoresearchBenchmarkContract {
206
152
  const nestedLine = lines[nestedIndex] ?? "";
207
153
  if (nestedLine.match(KEY_VALUE_REGEX)) break;
208
154
  const nestedMatch = nestedLine.match(/^\s{2,}[-*]\s+(.*)$/);
209
- if (!nestedMatch) {
210
- if (nestedLine.trim().length > 0) break;
211
- continue;
212
- }
155
+ if (!nestedMatch && nestedLine.trim().length > 0) break;
156
+ if (!nestedMatch) continue;
213
157
  nestedItems.push((nestedMatch[1] ?? "").trim());
214
158
  index = nestedIndex;
215
159
  }
216
- if (nestedItems.length > 0) {
217
- value = [value, ...nestedItems].filter(Boolean).join(", ");
218
- }
160
+ if (nestedItems.length > 0) value = [value, ...nestedItems].filter(Boolean).join(", ");
219
161
  }
220
162
  entries.set(key, value);
221
163
  }
222
164
 
223
- const direction = parseDirection(entries.get("direction"));
165
+ const directionRaw = entries.get("direction");
166
+ const direction: MetricDirection | null =
167
+ directionRaw === "lower" || directionRaw === "higher" ? directionRaw : null;
224
168
  return {
225
- command: readNullableEntry(entries.get("command")),
226
- primaryMetric: readNullableEntry(entries.get("primarymetric")),
169
+ command: entries.get("command")?.trim() || null,
170
+ primaryMetric: entries.get("primarymetric")?.trim() || null,
227
171
  metricUnit: entries.get("metricunit")?.trim() ?? "",
228
172
  direction,
229
- secondaryMetrics: parseSecondaryMetrics(entries.get("secondarymetrics")),
173
+ secondaryMetrics: entries.get("secondarymetrics")
174
+ ? normalizeAutoresearchList(
175
+ entries
176
+ .get("secondarymetrics")!
177
+ .split(",")
178
+ .map(e => e.trim())
179
+ .filter(Boolean),
180
+ )
181
+ : [],
230
182
  };
231
183
  }
232
184
 
@@ -252,37 +204,32 @@ function parseListSection(section: string, normalizeItem?: (value: string) => st
252
204
  }
253
205
  items.push(line.trim());
254
206
  }
255
- if (activeItem) {
256
- items.push(activeItem);
257
- }
207
+ if (activeItem) items.push(activeItem);
258
208
  const normalizedItems = normalizeAutoresearchList(items);
259
209
  return normalizeItem ? normalizedItems.map(normalizeItem) : normalizedItems;
260
210
  }
261
-
262
- function normalizeKey(value: string): string {
263
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
264
- }
265
-
266
- function parseDirection(value: string | undefined): MetricDirection | null {
267
- if (value === "lower" || value === "higher") return value;
268
- return null;
269
- }
270
-
271
- function readNullableEntry(value: string | undefined): string | null {
272
- const trimmed = value?.trim() ?? "";
273
- return trimmed.length > 0 ? trimmed : null;
274
- }
275
-
276
- function parseSecondaryMetrics(value: string | undefined): string[] {
277
- if (!value) return [];
278
- return normalizeAutoresearchList(
279
- value
280
- .split(",")
281
- .map(entry => entry.trim())
282
- .filter(Boolean),
283
- );
284
- }
285
-
286
211
  function isUnsafeContractPathSpec(value: string): boolean {
287
212
  return path.posix.isAbsolute(value) || value === ".." || value.startsWith("../");
288
213
  }
214
+
215
+ /**
216
+ * Updates session fields from a validated `autoresearch.md` parse (same fields as `init_experiment`).
217
+ * Does not touch `name`, `currentSegment`, `results`, `bestMetric`, `confidence`, or `maxExperiments`.
218
+ */
219
+ export function applyAutoresearchContractToExperimentState(
220
+ contract: AutoresearchContract,
221
+ state: ExperimentState,
222
+ ): void {
223
+ const benchmarkContract = contract.benchmark;
224
+ state.metricName = benchmarkContract.primaryMetric ?? state.metricName;
225
+ state.metricUnit = benchmarkContract.metricUnit;
226
+ state.bestDirection = benchmarkContract.direction ?? "lower";
227
+ state.secondaryMetrics = benchmarkContract.secondaryMetrics.map(name => ({
228
+ name,
229
+ unit: inferMetricUnitFromName(name),
230
+ }));
231
+ state.benchmarkCommand = benchmarkContract.command?.trim() ?? state.benchmarkCommand;
232
+ state.scopePaths = [...contract.scopePaths];
233
+ state.offLimits = [...contract.offLimits];
234
+ state.constraints = [...contract.constraints];
235
+ }
@@ -24,9 +24,7 @@ export function createDashboardController(): DashboardController {
24
24
  return {
25
25
  clear(ctx): void {
26
26
  clear();
27
- if (ctx.hasUI) {
28
- ctx.ui.setWidget("autoresearch", undefined);
29
- }
27
+ if (ctx.hasUI) ctx.ui.setWidget("autoresearch", undefined);
30
28
  },
31
29
  requestRender,
32
30
  updateWidget(ctx, runtime): void {
@@ -38,9 +36,8 @@ export function createDashboardController(): DashboardController {
38
36
  }
39
37
 
40
38
  ctx.ui.setWidget("autoresearch", (_tui, theme) => {
41
- if (state.results.length === 0 && runtime.runningExperiment) {
39
+ if (state.results.length === 0 && runtime.runningExperiment)
42
40
  return new Text(renderRunningOnly(runtime, state, theme), 0, 0);
43
- }
44
41
  if (runtime.dashboardExpanded) {
45
42
  const width = process.stdout.columns ?? 120;
46
43
  const lines = [
@@ -70,9 +67,8 @@ export function createDashboardController(): DashboardController {
70
67
  const terminalRows = process.stdout.rows ?? 40;
71
68
  const header = renderExpandedHeader(runtime, width, theme);
72
69
  const body = renderDashboardLines(runtime, width, theme, 0);
73
- if (runtime.runningExperiment) {
70
+ if (runtime.runningExperiment)
74
71
  body.push(renderOverlayRunningLine(runtime, theme, width, spinnerFrame));
75
- }
76
72
  const viewportRows = Math.max(4, terminalRows - 4);
77
73
  const maxScroll = Math.max(0, body.length - viewportRows);
78
74
  if (scrollOffset > maxScroll) scrollOffset = maxScroll;
@@ -124,12 +120,8 @@ export function createDashboardController(): DashboardController {
124
120
 
125
121
  function renderRunningOnly(runtime: AutoresearchRuntime, state: ExperimentState, theme: Theme): string {
126
122
  const parts = [theme.fg("contentAccent", "autoresearch"), theme.fg("warning", " running...")];
127
- if (state.name) {
128
- parts.push(theme.fg("dim", ` | ${replaceTabs(state.name)}`));
129
- }
130
- if (runtime.runningExperiment) {
131
- parts.push(theme.fg("dim", ` | ${replaceTabs(runtime.runningExperiment.command)}`));
132
- }
123
+ if (state.name) parts.push(theme.fg("dim", ` | ${replaceTabs(state.name)}`));
124
+ if (runtime.runningExperiment) parts.push(theme.fg("dim", ` | ${replaceTabs(runtime.runningExperiment.command)}`));
133
125
  return parts.join("");
134
126
  }
135
127
 
@@ -170,20 +162,14 @@ function renderCollapsedLine(runtime: AutoresearchRuntime, state: ExperimentStat
170
162
  );
171
163
  }
172
164
  parts.push(theme.fg("warning", " | log_experiment required"));
173
- if (!runtime.autoresearchMode) {
174
- parts.push(theme.fg("dim", " | mode off"));
175
- }
165
+ if (!runtime.autoresearchMode) parts.push(theme.fg("dim", " | mode off"));
176
166
  return parts.join("");
177
167
  }
178
168
  if (state.results.length === 0) {
179
169
  const modeStatus = runtime.autoresearchMode ? "baseline pending" : "mode off";
180
170
  const parts = [theme.fg("contentAccent", "autoresearch"), theme.fg("warning", ` ${modeStatus}`)];
181
- if (state.name) {
182
- parts.push(theme.fg("dim", ` | ${replaceTabs(state.name)}`));
183
- }
184
- if (runtime.autoresearchMode) {
185
- parts.push(theme.fg("dim", " | run the baseline"));
186
- }
171
+ if (state.name) parts.push(theme.fg("dim", ` | ${replaceTabs(state.name)}`));
172
+ if (runtime.autoresearchMode) parts.push(theme.fg("dim", " | run the baseline"));
187
173
  return parts.join("");
188
174
  }
189
175
  const current = currentResults(state.results, state.currentSegment);
@@ -223,12 +209,7 @@ function renderCollapsedLine(runtime: AutoresearchRuntime, state: ExperimentStat
223
209
  return parts.join("");
224
210
  }
225
211
 
226
- export function renderDashboardLines(
227
- runtime: AutoresearchRuntime,
228
- width: number,
229
- theme: Theme,
230
- maxRows: number,
231
- ): string[] {
212
+ function renderDashboardLines(runtime: AutoresearchRuntime, width: number, theme: Theme, maxRows: number): string[] {
232
213
  const state = runtime.state;
233
214
  if (state.results.length === 0) {
234
215
  if (runtime.lastRunSummary) {
@@ -240,9 +221,7 @@ export function renderDashboardLines(
240
221
  ),
241
222
  truncateToWidth("Next action: finish log_experiment before starting another run.", width),
242
223
  ];
243
- if (!runtime.autoresearchMode) {
244
- lines.push(truncateToWidth("Mode: off", width));
245
- }
224
+ if (!runtime.autoresearchMode) lines.push(truncateToWidth("Mode: off", width));
246
225
  return lines;
247
226
  }
248
227
  if (runtime.autoresearchMode) {
@@ -287,20 +266,13 @@ export function renderDashboardLines(
287
266
  ),
288
267
  );
289
268
  }
290
- if (!runtime.autoresearchMode) {
291
- lines.push(truncateToWidth(`Mode: ${renderModeStatus(runtime, state)}`, width));
292
- }
269
+ if (!runtime.autoresearchMode) lines.push(truncateToWidth(`Mode: ${renderModeStatus(runtime, state)}`, width));
293
270
  if (best) {
294
271
  const bestRunNumber = best.result.runNumber ?? best.index + 1;
295
272
  let progress = `Best: ${formatNum(best.result.metric, state.metricUnit)} (#${bestRunNumber})`;
296
- if (baseline !== null && baseline !== 0 && best.result.metric !== baseline) {
297
- const delta = ((best.result.metric - baseline) / baseline) * 100;
298
- const sign = delta > 0 ? "+" : "";
299
- progress += ` ${sign}${delta.toFixed(1)}%`;
300
- }
301
- if (state.confidence !== null) {
302
- progress += ` conf ${state.confidence.toFixed(1)}x`;
303
- }
273
+ if (baseline !== null && baseline !== 0 && best.result.metric !== baseline)
274
+ progress += ` ${formatDelta(best.result.metric, baseline)}`;
275
+ if (state.confidence !== null) progress += ` conf ${state.confidence.toFixed(1)}x`;
304
276
  lines.push(truncateToWidth(progress, width));
305
277
  if (state.secondaryMetrics.length > 0) {
306
278
  const details = state.secondaryMetrics
@@ -313,9 +285,7 @@ export function renderDashboardLines(
313
285
  ),
314
286
  )
315
287
  .filter((value): value is string => Boolean(value));
316
- if (details.length > 0) {
317
- lines.push(truncateToWidth(`Secondary: ${details.join(" ")}`, width));
318
- }
288
+ if (details.length > 0) lines.push(truncateToWidth(`Secondary: ${details.join(" ")}`, width));
319
289
  }
320
290
  }
321
291
  lines.push("");
@@ -323,9 +293,8 @@ export function renderDashboardLines(
323
293
  lines.push(theme.fg("borderMuted", "-".repeat(Math.max(0, width - 1))));
324
294
 
325
295
  const visible = maxRows > 0 ? current.slice(-maxRows) : current;
326
- if (visible.length < current.length) {
296
+ if (visible.length < current.length)
327
297
  lines.push(theme.fg("dim", `... ${current.length - visible.length} earlier runs hidden ...`));
328
- }
329
298
  for (const result of visible) {
330
299
  lines.push(renderResultRow(result, state, baselineSecondary, width, theme));
331
300
  }
@@ -358,12 +327,12 @@ function renderResultRow(
358
327
  .join("");
359
328
  const statusColor = result.status === "keep" ? "success" : result.status === "discard" ? "warning" : "error";
360
329
  const line =
361
- `${theme.fg("dim", String(runNumber).padEnd(4))}` +
362
- `${theme.fg("contentAccent", (result.commit || "-").padEnd(10))}` +
363
- `${theme.fg(statusColor, formatNum(result.metric, state.metricUnit).padEnd(12))}` +
364
- `${secondary}` +
365
- `${theme.fg(statusColor, result.status.padEnd(14))}` +
366
- `${theme.fg("muted", replaceTabs(result.description))}`;
330
+ theme.fg("dim", String(runNumber).padEnd(4)) +
331
+ theme.fg("contentAccent", (result.commit || "-").padEnd(10)) +
332
+ theme.fg(statusColor, formatNum(result.metric, state.metricUnit).padEnd(12)) +
333
+ secondary +
334
+ theme.fg(statusColor, result.status.padEnd(14)) +
335
+ theme.fg("muted", replaceTabs(result.description));
367
336
  return truncateToWidth(line, width);
368
337
  }
369
338
 
@@ -371,9 +340,7 @@ function renderSecondaryCell(value: number | undefined, unit: string, baseline:
371
340
  if (value === undefined) return "-";
372
341
  const formatted = formatNum(value, unit);
373
342
  if (baseline === undefined || baseline === 0 || baseline === value) return formatted;
374
- const delta = ((value - baseline) / baseline) * 100;
375
- const sign = delta > 0 ? "+" : "";
376
- return `${formatted} ${sign}${delta.toFixed(1)}%`;
343
+ return `${formatted} ${formatDelta(value, baseline)}`;
377
344
  }
378
345
 
379
346
  function renderSecondarySummary(
@@ -383,12 +350,12 @@ function renderSecondarySummary(
383
350
  unit: string,
384
351
  ): string | null {
385
352
  if (value === undefined) return null;
386
- if (baseline === undefined || baseline === 0 || baseline === value) {
387
- return `${name} ${formatNum(value, unit)}`;
388
- }
353
+ return `${name} ${renderSecondaryCell(value, unit, baseline)}`;
354
+ }
355
+
356
+ function formatDelta(value: number, baseline: number): string {
389
357
  const delta = ((value - baseline) / baseline) * 100;
390
- const sign = delta > 0 ? "+" : "";
391
- return `${name} ${formatNum(value, unit)} ${sign}${delta.toFixed(1)}%`;
358
+ return `${delta > 0 ? "+" : ""}${delta.toFixed(1)}%`;
392
359
  }
393
360
 
394
361
  function renderOverlayRunningLine(
@@ -426,13 +393,9 @@ function renderOverlayFooter(
426
393
  }
427
394
 
428
395
  function renderModeStatus(runtime: AutoresearchRuntime, state: ExperimentState): string {
429
- if (runtime.autoresearchMode) {
430
- return state.results.length === 0 ? "baseline pending" : "mode on";
431
- }
396
+ if (runtime.autoresearchMode) return state.results.length === 0 ? "baseline pending" : "mode on";
432
397
  const current = currentResults(state.results, state.currentSegment);
433
- if (state.maxExperiments !== null && current.length >= state.maxExperiments) {
434
- return "segment complete";
435
- }
398
+ if (state.maxExperiments !== null && current.length >= state.maxExperiments) return "segment complete";
436
399
  return "mode off";
437
400
  }
438
401
 
@@ -441,9 +404,7 @@ function findBestResult(state: ExperimentState): { index: number; result: Experi
441
404
  for (let index = 0; index < state.results.length; index += 1) {
442
405
  const result = state.results[index];
443
406
  if (result.segment !== state.currentSegment || result.status !== "keep" || result.metric <= 0) continue;
444
- if (!best || isBetter(result.metric, best.result.metric, state.bestDirection)) {
445
- best = { index, result };
446
- }
407
+ if (!best || isBetter(result.metric, best.result.metric, state.bestDirection)) best = { index, result };
447
408
  }
448
409
  return best;
449
410
  }