feat: add project-management web-probe observe

This commit is contained in:
Codex
2026-06-25 11:39:53 +00:00
parent 8d26e6fbc3
commit 80351ce5d2
9 changed files with 1070 additions and 23 deletions
@@ -19,6 +19,7 @@ const analyzeTailSamples = (() => {
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 360;
})();
const alertThresholds = parseAlertThresholds(process.env.UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON);
const projectManagementConfig = parseProjectManagementConfig(process.env.UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON);
const dataDir = archivePrefix ? path.join(stateDir, "archive") : stateDir;
const dataFile = (name) => path.join(dataDir, archivePrefix ? archivePrefix + "-" + name : name);
const analysisDir = path.join(stateDir, "analysis");
@@ -53,6 +54,7 @@ const pageProvenance = buildPageProvenanceReport(samples, control, manifest);
const pagePerformance = buildPagePerformanceReport(samples, manifest);
const promptNetwork = buildPromptNetworkReport(control, promptNetworkRows);
const runtimeAlerts = buildRuntimeAlerts(samples, control, network, consoleEvents, errors);
const projectManagement = buildProjectManagementReport(samples, control, network, pagePerformance, projectManagementConfig);
const runnerErrors = errors.slice(-8).map((item) => {
const attempts = Array.isArray(item.error?.attempts) ? item.error.attempts : [];
const lastAttempt = attempts.length > 0 ? attempts[attempts.length - 1] : null;
@@ -95,7 +97,7 @@ const runnerErrors = errors.slice(-8).map((item) => {
});
const commandFailures = summarizeCommandFailures(control);
const toolFindings = buildToolFindings({ manifest, heartbeat, commandState });
const findings = [...toolFindings, ...buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance, commandFailures)];
const findings = [...toolFindings, ...buildProjectManagementFindings(projectManagement), ...buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance, commandFailures)];
if (jsonlReadIssues.length > 0) findings.unshift({ id: "jsonl-read-issues", severity: "red", summary: "observer analyzer hit JSONL read/parse issues", count: jsonlReadIssues.length, issues: jsonlReadIssues.slice(0, 20) });
const recentWindow = buildRecentAnalysisWindow({ samples, control, network, consoleEvents, errors, manifest });
const commandTimeline = control.filter((item) => item.phase === "completed" || item.phase === "failed").map((item) => ({ ts: item.ts, phase: item.phase, commandId: item.commandId, type: item.type, input: item.input, afterUrl: item.afterUrl }));
@@ -114,6 +116,7 @@ const report = {
sampleMetrics,
pageProvenance,
pagePerformance,
projectManagement,
promptNetwork,
runtimeAlerts,
runnerErrors,
@@ -142,6 +145,7 @@ console.log(JSON.stringify({
archiveSummary: {
sampleMetrics: sampleMetrics.summary,
pagePerformance: pagePerformance.summary,
projectManagement: projectManagement.summary,
runtimeAlerts: runtimeAlerts.summary,
findingCount: findings.length,
redFindingCount: findings.filter((item) => item.severity === "red").length,
@@ -159,6 +163,7 @@ console.log(JSON.stringify({
},
pageProvenance: recentWindow.pageProvenance.summary,
pagePerformance: recentWindow.pagePerformance.summary,
projectManagement: compactProjectManagementForOutput(projectManagement),
promptNetwork: recentWindow.promptNetwork.summary,
runtimeAlerts: recentWindow.runtimeAlerts.summary,
runnerErrors,
@@ -538,6 +543,47 @@ function parseAlertThresholds(value) {
};
}
function parseProjectManagementConfig(value) {
if (!value || value === "null") {
return {
enabled: false,
targetPaths: [],
readinessSelectors: [],
naturalApiPathPrefixes: [],
commandAllowlist: [],
launchRoute: "",
slowApiBudgetMs: 0,
source: "yaml-env",
valuesRedacted: true
};
}
const raw = (() => {
try { return JSON.parse(value); } catch (error) { throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error))); }
})();
if (raw?.enabled !== true && raw?.enabled !== false) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON requires boolean enabled");
if (raw.enabled !== true) return { enabled: false, targetPaths: [], readinessSelectors: [], naturalApiPathPrefixes: [], commandAllowlist: [], launchRoute: "", slowApiBudgetMs: 0, source: "yaml-env", valuesRedacted: true };
const stringList = (key) => {
const list = raw?.[key];
if (!Array.isArray(list) || list.some((item) => typeof item !== "string" || item.length === 0)) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON requires string[] " + key + "; configure config/hwlab-node-lanes.yaml webProbe.projectManagement");
return list;
};
const slowApiBudgetMs = Number(raw?.slowApiBudgetMs);
if (!Number.isFinite(slowApiBudgetMs) || slowApiBudgetMs <= 0) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON requires positive slowApiBudgetMs");
const launchRoute = String(raw.launchRoute || "");
if (!launchRoute.startsWith("/")) throw new Error("UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON launchRoute must be an absolute path");
return {
enabled: true,
targetPaths: stringList("targetPaths"),
readinessSelectors: stringList("readinessSelectors"),
naturalApiPathPrefixes: stringList("naturalApiPathPrefixes"),
commandAllowlist: stringList("commandAllowlist"),
launchRoute,
slowApiBudgetMs,
source: "yaml-env",
valuesRedacted: true
};
}
async function readJsonl(file, options = {}) {
const tailLimit = Number.isFinite(Number(options.tail)) && Number(options.tail) > 0 ? Math.floor(Number(options.tail)) : 0;
if (tailLimit > 0) return readJsonlTail(file, tailLimit, options);
@@ -712,11 +758,38 @@ function compactSampleForAnalysis(sample) {
sessionRail: compactSessionRail(sample.sessionRail),
turns: compactDomItems(sample.turns),
diagnostics: compactDomItems(sample.diagnostics),
projectManagement: compactProjectManagementSample(sample.projectManagement),
pageProvenance: compactSamplePageProvenance(sample.pageProvenance),
performance: compactPerformanceItems(sample.performance)
};
}
function compactProjectManagementSample(value) {
if (!value || typeof value !== "object") return null;
return {
pageKind: value.pageKind ?? null,
configuredPath: value.configuredPath === true,
rootVisible: value.rootVisible === true,
mdtodoVisible: value.mdtodoVisible === true,
sourceCount: value.sourceCount ?? null,
fileCount: value.fileCount ?? null,
taskCount: value.taskCount ?? null,
taskRefMissingCount: value.taskRefMissingCount ?? null,
selectedSourceId: value.selectedSourceId ?? null,
selectedFileRef: value.selectedFileRef ?? null,
selectedTaskRef: value.selectedTaskRef ?? null,
selectedTaskStatus: value.selectedTaskStatus ?? null,
taskStatusCounts: value.taskStatusCounts && typeof value.taskStatusCounts === "object" ? value.taskStatusCounts : {},
launchButtonVisible: value.launchButtonVisible === true,
launchButtonEnabled: value.launchButtonEnabled === true,
launchButtonText: value.launchButtonText ?? null,
blockerCount: value.blockerCount ?? 0,
blockers: Array.isArray(value.blockers) ? value.blockers.slice(0, 6) : [],
workbenchLinkCount: value.workbenchLinkCount ?? 0,
valuesRedacted: true
};
}
function compactSessionRail(value) {
if (!value || typeof value !== "object") return null;
const items = Array.isArray(value.items) ? value.items.slice(0, 80).map((item) => ({
@@ -917,6 +990,273 @@ function summarizeCommandFailures(control) {
});
}
function buildProjectManagementReport(samples, control, network, pagePerformance, config) {
const enabled = config?.enabled === true;
const targetPathSamples = (samples || []).filter((sample) => enabled && config.targetPaths.some((target) => String(sample?.path || "").startsWith(target)));
const projectSamples = (samples || []).filter((sample) => sample?.projectManagement && typeof sample.projectManagement === "object");
const latest = projectSamples[projectSamples.length - 1] || null;
const latestProject = latest?.projectManagement || null;
const pageKindCounts = countBy(projectSamples.map((sample) => sample.projectManagement?.pageKind).filter(Boolean));
const latestTaskStatusCounts = latestProject?.taskStatusCounts && typeof latestProject.taskStatusCounts === "object" ? latestProject.taskStatusCounts : {};
const commandRows = projectManagementCommandRows(control, config);
const launchCommands = commandRows.filter((item) => item.type === "launchWorkbenchFromTask");
const launchSuccess = launchCommands.filter((item) => item.phase === "completed" && Number(item.launchStatus ?? 0) >= 200 && Number(item.launchStatus ?? 0) < 300);
const launchFailed = launchCommands.filter((item) => item.phase === "failed" || Number(item.launchStatus ?? 200) >= 400);
const projectApiEvents = projectManagementNetworkRows(network, config);
const projectApiResponses = projectApiEvents.filter((item) => item.type === "response");
const projectApiFailures = projectApiResponses.filter((item) => Number(item.status ?? 0) >= 400);
const projectApiFailedRequests = projectApiEvents.filter((item) => item.type === "requestfailed");
const projectApiByPath = groupProjectApiEvents(projectApiEvents);
const projectApiPerformance = projectManagementPerformanceRows(pagePerformance, config);
const slowProjectApiPerformance = projectApiPerformance.filter((item) => Number(item.overBudgetCount ?? 0) > 0 || Number(item.p95Ms ?? 0) > Number(config?.slowApiBudgetMs ?? 0));
const selectedTaskSamples = projectSamples.filter((sample) => sample.projectManagement?.selectedTaskRef?.hash);
const launchEnabledSamples = projectSamples.filter((sample) => sample.projectManagement?.launchButtonEnabled === true);
const launchVisibleSamples = projectSamples.filter((sample) => sample.projectManagement?.launchButtonVisible === true);
const mdtodoSamples = projectSamples.filter((sample) => sample.projectManagement?.pageKind === "project-management-mdtodo");
return {
enabled,
config: config || null,
summary: {
enabled,
targetPathSampleCount: targetPathSamples.length,
projectSampleCount: projectSamples.length,
mdtodoSampleCount: mdtodoSamples.length,
pageKindCounts,
latestPageKind: latestProject?.pageKind ?? null,
latestPath: latest?.path ?? null,
latestSeq: latest?.seq ?? null,
latestTs: latest?.ts ?? null,
latestSourceCount: latestProject?.sourceCount ?? null,
latestFileCount: latestProject?.fileCount ?? null,
latestTaskCount: latestProject?.taskCount ?? null,
maxSourceCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.sourceCount)),
maxFileCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.fileCount)),
maxTaskCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.taskCount)),
taskRefMissingMax: maxNumber(projectSamples.map((sample) => sample.projectManagement?.taskRefMissingCount)),
latestSelectedTaskRefHash: latestProject?.selectedTaskRef?.hash ?? null,
latestSelectedTaskRefPreview: latestProject?.selectedTaskRef?.preview ?? null,
latestSelectedTaskStatus: latestProject?.selectedTaskStatus ?? null,
latestTaskStatusCounts,
launchButtonVisibleSamples: launchVisibleSamples.length,
launchButtonEnabledSamples: launchEnabledSamples.length,
launchButtonDisabledSamples: Math.max(0, launchVisibleSamples.length - launchEnabledSamples.length),
latestWorkbenchLinkCount: latestProject?.workbenchLinkCount ?? null,
maxWorkbenchLinkCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.workbenchLinkCount)),
maxBlockerCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.blockerCount)),
selectedTaskSampleCount: selectedTaskSamples.length,
projectCommandCount: commandRows.length,
launchCommandCount: launchCommands.length,
launchSuccessCount: launchSuccess.length,
launchFailureCount: launchFailed.length,
launchWithOtelTraceHeaderCount: launchSuccess.filter((item) => item.otelTraceId).length,
projectApiEventCount: projectApiEvents.length,
projectApiResponseCount: projectApiResponses.length,
projectApiFailureCount: projectApiFailures.length,
projectApiRequestFailedCount: projectApiFailedRequests.length,
projectApiSlowPathCount: slowProjectApiPerformance.length,
slowApiBudgetMs: config?.slowApiBudgetMs ?? null,
valuesRedacted: true
},
latest: latestProject,
samples: projectSamples.slice(-80).map((sample) => ({
seq: sample.seq ?? null,
ts: sample.ts ?? null,
pageRole: sample.pageRole ?? null,
path: sample.path ?? null,
pageKind: sample.projectManagement?.pageKind ?? null,
sourceCount: sample.projectManagement?.sourceCount ?? null,
fileCount: sample.projectManagement?.fileCount ?? null,
taskCount: sample.projectManagement?.taskCount ?? null,
taskRefMissingCount: sample.projectManagement?.taskRefMissingCount ?? null,
selectedTaskRefHash: sample.projectManagement?.selectedTaskRef?.hash ?? null,
selectedTaskStatus: sample.projectManagement?.selectedTaskStatus ?? null,
launchButtonVisible: sample.projectManagement?.launchButtonVisible === true,
launchButtonEnabled: sample.projectManagement?.launchButtonEnabled === true,
blockerCount: sample.projectManagement?.blockerCount ?? 0,
workbenchLinkCount: sample.projectManagement?.workbenchLinkCount ?? 0,
valuesRedacted: true
})),
targetPathWithoutProjectSummary: targetPathSamples.filter((sample) => !sample.projectManagement).slice(0, 20).map(ref),
commands: commandRows,
launchCommands,
projectApiByPath,
projectApiFailures: projectApiFailures.slice(0, 40),
projectApiRequestFailed: projectApiFailedRequests.slice(0, 40),
projectApiPerformance,
slowProjectApiPerformance,
valuesRedacted: true
};
}
function compactProjectManagementForOutput(report) {
if (!report || typeof report !== "object") return null;
const compactCommand = (item) => ({
ts: item?.ts ?? null,
phase: item?.phase ?? null,
type: item?.type ?? null,
commandId: item?.commandId ?? null,
afterPath: item?.afterPath ?? null,
launchStatus: item?.launchStatus ?? null,
sessionId: item?.sessionId ?? null,
workbenchUrl: item?.workbenchUrl ?? null,
otelTraceId: item?.otelTraceId ?? null,
contractVersion: item?.contractVersion ?? null,
selectedTaskRefHash: item?.selectedTaskRefHash ?? null,
errorMessageHash: item?.errorMessageHash ?? null,
message: item?.message ? limitText(item.message, 180) : null,
valuesRedacted: true
});
const compactApiGroup = (item) => ({
method: item?.method ?? null,
path: item?.path ?? item?.urlPath ?? null,
status: item?.status ?? null,
type: item?.type ?? null,
count: item?.count ?? item?.sampleCount ?? null,
firstAt: item?.firstAt ?? null,
lastAt: item?.lastAt ?? null,
failureKinds: Array.isArray(item?.failureKinds) ? item.failureKinds.slice(0, 4) : [],
valuesRedacted: true
});
const compactSlowSample = (item) => ({
ts: item?.ts ?? null,
seq: item?.seq ?? null,
path: item?.path ?? item?.rawPath ?? null,
durationMs: item?.durationMs ?? null,
requestToResponseStartMs: item?.requestToResponseStartMs ?? item?.streamOpenMs ?? null,
responseTransferMs: item?.responseTransferMs ?? null,
timingStatus: item?.timingStatus ?? null,
initiatorType: item?.initiatorType ?? null,
nextHopProtocol: item?.nextHopProtocol ?? null,
serverTimingNames: Array.isArray(item?.serverTimingNames) ? item.serverTimingNames.slice(0, 4) : [],
otelTraceId: item?.otelTraceId ?? null,
valuesRedacted: true
});
const compactPerformance = (item) => ({
path: item?.path ?? item?.route ?? null,
sampleCount: item?.sampleCount ?? null,
p95Ms: item?.p95Ms ?? item?.p95 ?? null,
maxMs: item?.maxMs ?? item?.max ?? null,
budgetMs: item?.projectSlowBudgetMs ?? item?.budgetMs ?? report.summary?.slowApiBudgetMs ?? null,
overBudgetCount: item?.overBudgetCount ?? item?.overFiveSecondCount ?? null,
slowSamples: Array.isArray(item?.slowSamples) ? item.slowSamples.slice(0, 3).map(compactSlowSample) : [],
valuesRedacted: true
});
return {
summary: report.summary ?? null,
samples: Array.isArray(report.samples) ? report.samples.slice(-8) : [],
commands: Array.isArray(report.commands) ? report.commands.slice(-8).map(compactCommand) : [],
launchCommands: Array.isArray(report.launchCommands) ? report.launchCommands.slice(-8).map(compactCommand) : [],
projectApiByPath: Array.isArray(report.projectApiByPath) ? report.projectApiByPath.slice(0, 8).map(compactApiGroup) : [],
projectApiPerformance: Array.isArray(report.projectApiPerformance) ? report.projectApiPerformance.slice(0, 8).map(compactPerformance) : [],
slowProjectApiPerformance: Array.isArray(report.slowProjectApiPerformance) ? report.slowProjectApiPerformance.slice(0, 8).map(compactPerformance) : [],
valuesRedacted: true
};
}
function projectManagementCommandRows(control, config) {
const allowed = new Set(config?.commandAllowlist || []);
return (control || [])
.filter((item) => allowed.has(item?.type) || String(item?.type || "").startsWith("selectMdtodo") || item?.type === "selectProjectSource" || item?.type === "launchWorkbenchFromTask")
.filter((item) => item.phase === "completed" || item.phase === "failed")
.map((item) => {
const detail = item.detail && typeof item.detail === "object" ? item.detail : {};
const error = detail.error && typeof detail.error === "object" ? detail.error : {};
return {
ts: item.ts ?? null,
phase: item.phase ?? null,
type: item.type ?? null,
commandId: item.commandId ?? null,
afterPath: urlPath(item.afterUrl),
launchStatus: detail.launchStatus ?? error.details?.launchStatus ?? null,
sessionId: detail.sessionId ?? error.details?.sessionId ?? null,
workbenchUrl: detail.workbenchUrl ?? error.details?.workbenchUrl ?? null,
otelTraceId: detail.otelTraceId ?? error.details?.otelTraceId ?? null,
contractVersion: detail.contractVersion ?? error.details?.contractVersion ?? null,
selectedTaskRefHash: detail.selectedTask?.hash ?? detail.projectBeforeClick?.selectedTaskRef?.hash ?? null,
errorName: error.name ?? null,
errorMessageHash: error.message ? sha256(error.message) : null,
message: error.message ? limitText(error.message, 180) : null,
valuesRedacted: true
};
});
}
function projectManagementNetworkRows(network, config) {
const prefixes = config?.naturalApiPathPrefixes || [];
return (network || [])
.filter((item) => item?.observerInitiated !== true)
.map((item) => ({
ts: item.ts ?? null,
type: item.type ?? null,
method: String(item.method || "GET").toUpperCase(),
status: Number.isFinite(Number(item.status)) ? Number(item.status) : null,
path: urlPath(item.url),
failureKind: item.failure ? limitText(item.failure, 120) : null,
valuesRedacted: true
}))
.filter((item) => prefixes.some((prefix) => String(item.path || "").startsWith(prefix)));
}
function groupProjectApiEvents(events) {
const groups = new Map();
for (const item of events || []) {
const key = [item.method, item.path, item.status ?? "-", item.type].join(" ");
const existing = groups.get(key) || { method: item.method, path: item.path, status: item.status, type: item.type, count: 0, firstAt: item.ts, lastAt: item.ts, failureKinds: [], valuesRedacted: true };
existing.count += 1;
existing.lastAt = item.ts;
if (item.failureKind && !existing.failureKinds.includes(item.failureKind)) existing.failureKinds.push(item.failureKind);
groups.set(key, existing);
}
return Array.from(groups.values()).sort((a, b) => b.count - a.count || String(a.path).localeCompare(String(b.path)));
}
function projectManagementPerformanceRows(pagePerformance, config) {
const prefixes = config?.naturalApiPathPrefixes || [];
const rows = Array.isArray(pagePerformance?.sameOriginApiByPath) ? pagePerformance.sameOriginApiByPath : [];
return rows
.filter((item) => prefixes.some((prefix) => String(item?.path || "").startsWith(prefix)))
.map((item) => ({ ...item, projectSlowBudgetMs: config?.slowApiBudgetMs ?? null }));
}
function buildProjectManagementFindings(report) {
if (!report?.enabled) return [];
const findings = [];
const summary = report.summary || {};
if (Number(summary.targetPathSampleCount ?? 0) > 0 && Number(summary.projectSampleCount ?? 0) === 0) {
findings.push({ id: "project-management-route-not-ready", severity: "red", summary: "project management target path was sampled but no project-management DOM summary was detected", count: summary.targetPathSampleCount, samples: report.targetPathWithoutProjectSummary, valuesRedacted: true });
}
if (Number(summary.taskRefMissingMax ?? 0) > 0) {
findings.push({ id: "mdtodo-taskref-missing", severity: "red", summary: "mdtodo task rows were visible without stable data-task-ref; Workbench launch must bind by opaque public task id", count: summary.taskRefMissingMax, samples: report.samples.filter((item) => Number(item.taskRefMissingCount ?? 0) > 0).slice(0, 20), valuesRedacted: true });
}
if (Number(summary.mdtodoSampleCount ?? 0) > 0 && Number(summary.latestTaskCount ?? 0) > 0 && Number(summary.launchButtonEnabledSamples ?? 0) === 0) {
findings.push({ id: "workbench-launch-button-unavailable", severity: "red", summary: "mdtodo tasks were sampled but the Workbench launch button was never enabled", count: summary.mdtodoSampleCount, latest: report.latest, valuesRedacted: true });
}
if (Number(summary.projectApiFailureCount ?? 0) > 0 || Number(summary.projectApiRequestFailedCount ?? 0) > 0) {
findings.push({ id: "project-management-api-failed", severity: "amber", summary: "natural project-management or Workbench launch API requests failed during observation", count: Number(summary.projectApiFailureCount ?? 0) + Number(summary.projectApiRequestFailedCount ?? 0), groups: report.projectApiByPath.slice(0, 12), valuesRedacted: true });
}
if (Number(summary.projectApiSlowPathCount ?? 0) > 0) {
findings.push({ id: "project-management-api-slow", severity: "red", summary: "project-management API resource timing exceeded YAML projectManagement.slowApiBudgetMs", count: summary.projectApiSlowPathCount, budgetMs: summary.slowApiBudgetMs, groups: report.slowProjectApiPerformance.slice(0, 12), valuesRedacted: true });
}
if (Number(summary.launchFailureCount ?? 0) > 0) {
findings.push({ id: "mdtodo-workbench-launch-failed", severity: "red", summary: "launchWorkbenchFromTask command failed or returned an HTTP error", count: summary.launchFailureCount, commands: report.launchCommands.filter((item) => item.phase === "failed" || Number(item.launchStatus ?? 200) >= 400).slice(0, 12), valuesRedacted: true });
}
if (Number(summary.launchSuccessCount ?? 0) > 0 && Number(summary.launchWithOtelTraceHeaderCount ?? 0) === 0) {
findings.push({ id: "mdtodo-workbench-launch-otel-trace-missing", severity: "amber", summary: "Workbench launch succeeded but no x-hwlab-otel-trace-id header was captured for Tempo drill-down", count: summary.launchSuccessCount, commands: report.launchCommands.slice(0, 12), valuesRedacted: true });
}
return findings;
}
function countBy(values) {
const out = {};
for (const value of values || []) out[value] = (out[value] || 0) + 1;
return out;
}
function maxNumber(values) {
const numeric = (values || []).map((value) => Number(value)).filter(Number.isFinite);
return numeric.length > 0 ? Math.max(...numeric) : 0;
}
function buildFindings(samples, control, network, errors, sampleMetrics, promptNetwork, runtimeAlerts, pagePerformance, pageProvenance, commandFailures = []) {
const findings = [];
if (commandFailures.length > 0) findings.push({ id: "observer-command-failed", severity: "red", summary: "observer control commands failed; analyze must surface command failure instead of hiding it in command artifacts", count: commandFailures.length, commands: commandFailures.slice(0, 20) });
@@ -2140,6 +2480,7 @@ function prioritizeFindings(findings) {
};
const kindRank = (item) => {
const id = String(item?.id ?? item?.kind ?? item?.code ?? "");
if (id.startsWith("project-management-") || id.startsWith("mdtodo-") || id === "workbench-launch-button-unavailable") return 0;
if (id === "page-performance-slow-same-origin-api") return 0;
if (id === "session-rail-title-fallback-majority") return 0.5;
if (id.startsWith("code-agent-card-")) return 0.8;