Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions .github/scripts/terminal_disposition.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ function normalizeSourceId(value, fallback) {
return fallbackText || 'unknown';
}

function normalizeOptionalValue(key, value) {
if (value === null || value === undefined) return undefined;
if (key.endsWith('_number') || key === 'chain_depth' || key === 'verification_run_attempt') {
const parsed = cleanInt(value);
return parsed === null ? undefined : parsed;
}
if (key === 'needs_human' || key === 'depth_limit_exceeded') {
const parsed = cleanBool(value);
return parsed === null ? undefined : parsed;
}
const cleaned = typeof value === 'boolean' ? value : cleanString(value);
if (cleaned === '') return undefined;
return typeof value === 'string' ? cleaned : value;
}

function sourceKey(sourceType, sourceId) {
return `${normalizeSourceType(sourceType)}:${normalizeSourceId(sourceId)}`;
}
Expand Down Expand Up @@ -211,14 +226,13 @@ function normalizeTerminalDisposition(input = {}) {
dispatch_outcome: input.dispatch_outcome ?? input.dispatchOutcome,
llm_model: input.llm_model ?? input.llmModel ?? input.model,
model_selection_reason: input.model_selection_reason ?? input.modelSelectionReason,
llm_cli_version: input.llm_cli_version ?? input.llmCliVersion ?? input.cli_version,
verifier_mode: input.verifier_mode ?? input.verifierMode,
};

for (const [key, value] of Object.entries(optional)) {
if (value === null || value === undefined) continue;
const cleaned = typeof value === 'boolean' ? value : cleanString(value);
if (cleaned === '') continue;
record[key] = typeof value === 'string' ? cleaned : value;
const normalized = normalizeOptionalValue(key, value);
if (normalized !== undefined) record[key] = normalized;
}

return record;
Expand Down Expand Up @@ -273,15 +287,8 @@ function normalizeVerifierFollowupLedger(input = {}) {
};

for (const [key, value] of Object.entries(optional)) {
if (value === null || value === undefined) continue;
if (key.endsWith('_number') || key === 'chain_depth' || key === 'verification_run_attempt') {
const parsed = cleanInt(value);
if (parsed !== null) record[key] = parsed;
continue;
}
const cleaned = typeof value === 'boolean' ? value : cleanString(value);
if (cleaned === '') continue;
record[key] = typeof value === 'string' ? cleaned : value;
const normalized = normalizeOptionalValue(key, value);
if (normalized !== undefined) record[key] = normalized;
}

return record;
Expand Down Expand Up @@ -361,6 +368,7 @@ module.exports = {
normalizeVerifierFollowupLedger,
normalizeVerifierFollowupPolicy,
normalizeLedgerDisposition,
normalizeOptionalValue,
summarizeTerminalDispositionSources,
formatTerminalDispositionMarkdown,
sourceKey,
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/terminal_disposition_coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ function summarizeVerifierModelCompatibility(records = [], options = {}) {
const model = cleanString(record.llm_model ?? record.model).toLowerCase();
const reason = cleanString(record.model_selection_reason);
const verifierMode = cleanString(record.verifier_mode).toLowerCase();
const requiresCodexModel = Boolean(verifierMode) && verifierMode !== 'evaluate';
const requiresCodexModel = verifierMode !== 'evaluate';
if (model) selectedModels[model] = (selectedModels[model] || 0) + 1;
if (reason) modelSelectionReasons[reason] = (modelSelectionReasons[reason] || 0) + 1;
if (!model && requiresCodexModel && modelMetadataContract.model_metadata_required) {
Expand Down
3 changes: 3 additions & 0 deletions .github/scripts/weekly_metrics_artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ const EXACT_METRICS_ARTIFACTS = new Set([
'agents-autofix-metrics',
'agents-verifier-metrics',
'agents-verifier-disposition-metrics',
'codex-cli-freshness',
]);

const PREFIXED_METRICS_ARTIFACTS = [
'autopilot-metrics-',
'issue-optimizer-metrics-',
'issue-intake-format-metrics-',
'codex-cli-freshness-',
'verifier-terminal-disposition-',
'review-thread-terminal-disposition-',
];
Expand All @@ -34,6 +36,7 @@ const PATTERNED_METRICS_ARTIFACTS = [
];

const PRIORITY_METRICS_FAMILIES = [
'codex-cli-freshness',
'verifier-terminal-disposition',
'review-thread-terminal-disposition',
'bot-comment-auth-coverage-wrapper',
Expand Down
62 changes: 56 additions & 6 deletions .github/scripts/weekly_metrics_download_manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ function selectedArtifactsFromSelection(selection = {}) {
return Array.isArray(selection.selected_artifacts) ? selection.selected_artifacts : [];
}

function compactSelectionDetails(selection = {}, selectionPath = '') {
return {
path: selectionPath,
schema: cleanString(selection.schema),
status: cleanString(selection.status || 'pass'),
selected_count: selectedArtifactsFromSelection(selection).length,
candidate_count: Number.isFinite(Number(selection.candidate_count))
? Number(selection.candidate_count)
: 0,
candidate_family_counts: selection.candidate_family_counts || {},
selected_family_counts: selection.selected_family_counts || {},
missing_priority_families: Array.isArray(selection.missing_priority_families)
? selection.missing_priority_families
: [],
priority_family_statuses: Array.isArray(selection.priority_family_statuses)
? selection.priority_family_statuses
: [],
latest_candidate_by_family: selection.latest_candidate_by_family || {},
};
}

function defaultArtifactDir(root, artifact) {
return path.posix.join(root, safeArtifactPathSegment(artifact.name), String(artifact.id || ''));
}
Expand All @@ -56,12 +77,7 @@ function buildInitialManifest(selection = {}, options = {}) {
schema: DOWNLOAD_MANIFEST_SCHEMA,
status: selection.status === 'error' ? 'error' : 'pending',
generated_at: generatedAt,
selection: {
path: selectionPath,
schema: cleanString(selection.schema),
status: cleanString(selection.status || 'pass'),
selected_count: selected.length,
},
selection: compactSelectionDetails(selection, selectionPath),
stats: {
selected_count: selected.length,
download_pass_count: 0,
Expand Down Expand Up @@ -104,11 +120,44 @@ function findArtifact(manifest, id, name) {
});
}

function normalizeArtifactResultShape(artifact) {
if (!artifact || typeof artifact !== 'object') return;
if (!artifact.download || typeof artifact.download !== 'object') {
artifact.download = {
status: 'pending',
bytes: null,
error: '',
};
}
if (!artifact.unzip || typeof artifact.unzip !== 'object') {
artifact.unzip = {
status: 'pending',
path: artifact.artifact_dir || '',
error: '',
};
}
artifact.download.status = normalizeStatus(
artifact.download.status,
['pending', 'pass', 'failed', 'skipped'],
'pending'
);
artifact.unzip.status = normalizeStatus(
artifact.unzip.status,
['pending', 'pass', 'failed', 'skipped'],
'pending'
);
if (!artifact.unzip.path) artifact.unzip.path = artifact.artifact_dir || '';
if (artifact.download.bytes === undefined) artifact.download.bytes = null;
if (artifact.download.error === undefined) artifact.download.error = '';
if (artifact.unzip.error === undefined) artifact.unzip.error = '';
}

function updateArtifactResult(manifest, result = {}) {
const artifact = findArtifact(manifest, result.id, result.name);
if (!artifact) {
throw new Error(`Artifact is not present in manifest: ${cleanString(result.id || result.name)}`);
}
normalizeArtifactResultShape(artifact);
const artifactDir = cleanString(result.artifact_dir || result.artifactDir);
const zipPath = cleanString(result.zip_path || result.zipPath);
const zipBytes = Number.parseInt(cleanString(result.zip_bytes || result.zipBytes), 10);
Expand Down Expand Up @@ -313,6 +362,7 @@ if (require.main === module) {
module.exports = {
DOWNLOAD_MANIFEST_SCHEMA,
buildInitialManifest,
compactSelectionDetails,
finalizeManifest,
formatMarkdown,
safeArtifactPathSegment,
Expand Down
88 changes: 87 additions & 1 deletion scripts/aggregate_agent_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
"agents-autofix-metrics",
"agents-verifier-metrics",
"agents-verifier-disposition-metrics",
"codex-cli-freshness",
}
_PREFIXED_ARTIFACT_FAMILIES = (
"autopilot-metrics-",
"issue-optimizer-metrics-",
"issue-intake-format-metrics-",
"codex-cli-freshness-",
"verifier-terminal-disposition-",
"review-thread-terminal-disposition-",
)
Expand Down Expand Up @@ -323,6 +325,8 @@ def _classify_entry(entry: dict[str, Any]) -> str:
return "terminal_disposition"
if schema == "workflows-verifier-followup-ledger/v1":
return "verifier_followup_ledger"
if schema == "workflows-codex-cli-freshness/v1":
return "codex_cli_freshness"
explicit = entry.get("metric_type") or entry.get("type") or entry.get("workflow")
if isinstance(explicit, str):
lowered = explicit.lower()
Expand Down Expand Up @@ -506,6 +510,7 @@ def _summarise_verifier(
terminal_sources = Counter()
verifier_models = Counter()
model_selection_reasons = Counter()
verifier_cli_versions = Counter()
unsupported_verifier_models = Counter()
unsupported_model_dispositions = Counter()
missing_verifier_model_metadata = Counter()
Expand Down Expand Up @@ -563,7 +568,7 @@ def _summarise_verifier(
unsupported_model_dispositions[str(disposition)] += 1
elif is_verifier_terminal and model_metadata_required:
verifier_mode = str(entry.get("verifier_mode") or "").strip().lower()
if verifier_mode and verifier_mode != "evaluate":
if verifier_mode != "evaluate":
disposition = entry.get("disposition") or entry.get("terminal_state") or "unknown"
if _is_pre_contract_verifier_model_record(entry, model_metadata_required_after):
legacy_missing_verifier_model_metadata[str(disposition)] += 1
Expand All @@ -574,6 +579,14 @@ def _summarise_verifier(
)
if model_selection_reason:
model_selection_reasons[str(model_selection_reason)] += 1
cli_version = (
entry.get("codex_cli_version")
or entry.get("llm_cli_version")
or entry.get("cli_version")
)
cli_version_text = str(cli_version).strip() if cli_version is not None else ""
if cli_version_text:
verifier_cli_versions[cli_version_text.lower()] += 1
verifier_mode = str(entry.get("verifier_mode") or "").strip().lower()
if verifier_mode:
verifier_modes[verifier_mode] += 1
Expand Down Expand Up @@ -623,6 +636,7 @@ def _summarise_verifier(
"terminal_dispositions": terminal_dispositions,
"terminal_sources": terminal_sources,
"verifier_models": verifier_models,
"verifier_cli_versions": verifier_cli_versions,
"unsupported_verifier_models": unsupported_verifier_models,
"unsupported_model_dispositions": unsupported_model_dispositions,
"missing_verifier_model_metadata": missing_verifier_model_metadata,
Expand Down Expand Up @@ -728,6 +742,54 @@ def _summarise_autopilot(entries: list[dict[str, Any]]) -> dict[str, Any]:
}


def _summarise_codex_cli_freshness(entries: list[dict[str, Any]]) -> dict[str, Any]:
statuses = Counter()
packages = Counter()
pinned_versions = Counter()
latest_versions = Counter()
max_major_delta = 0
max_minor_delta = 0
max_patch_delta = 0
update_targets = Counter()
for entry in entries:
status = str(entry.get("status") or "unknown")
statuses[status] += 1
package = str(entry.get("package") or "unknown")
packages[package] += 1
pinned = str(entry.get("pinned_version") or "unknown")
latest = str(entry.get("latest_version") or "unknown")
pinned_versions[pinned] += 1
latest_versions[latest] += 1
delta = entry.get("version_delta")
if isinstance(delta, dict):
max_major_delta = max(max_major_delta, _safe_int(delta.get("major")) or 0)
max_minor_delta = max(max_minor_delta, _safe_int(delta.get("minor")) or 0)
max_patch_delta = max(max_patch_delta, _safe_int(delta.get("patch")) or 0)
targets = entry.get("update_targets")
if isinstance(targets, list):
for target in targets:
if not isinstance(target, dict):
continue
path = str(target.get("path") or "").strip()
if path:
update_targets[path] += 1
return {
"records": len(entries),
"statuses": statuses,
"packages": packages,
"pinned_versions": pinned_versions,
"latest_versions": latest_versions,
"outdated_records": statuses.get("outdated", 0),
"latest_unavailable_records": statuses.get("latest-unavailable", 0),
"max_version_delta": {
"major": max_major_delta,
"minor": max_minor_delta,
"patch": max_patch_delta,
},
"update_targets": update_targets,
}


def _format_counter(counter: Counter[str]) -> str:
if not counter:
return "n/a"
Expand Down Expand Up @@ -765,6 +827,7 @@ def _bucket_entries(entries: list[dict[str, Any]]) -> dict[str, list[dict[str, A
"verifier": [],
"terminal_disposition": [],
"verifier_followup_ledger": [],
"codex_cli_freshness": [],
"autopilot": [],
"unknown": [],
}
Expand All @@ -784,6 +847,7 @@ def _summary_metrics_contract(buckets: dict[str, list[dict[str, Any]]]) -> dict[
buckets["verifier_followup_ledger"],
),
"autopilot": _summarise_autopilot(buckets["autopilot"]),
"codex_cli_freshness": _summarise_codex_cli_freshness(buckets["codex_cli_freshness"]),
"unknown": {"records": len(buckets["unknown"])},
}
)
Expand Down Expand Up @@ -947,6 +1011,7 @@ def build_summary(
buckets["verifier_followup_ledger"],
)
autopilot = _summarise_autopilot(buckets["autopilot"])
codex_cli_freshness = _summarise_codex_cli_freshness(buckets["codex_cli_freshness"])

now = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
lines = [
Expand All @@ -959,6 +1024,7 @@ def build_summary(
f"verifier {verifier['runs']}, "
f"terminal dispositions {verifier['terminal_records']}, "
f"verifier follow-up ledgers {verifier['ledger_records']}, "
f"codex CLI freshness {codex_cli_freshness['records']}, "
f"autopilot {autopilot['records']}, "
f"unknown {len(buckets['unknown'])})"
),
Expand Down Expand Up @@ -1019,6 +1085,7 @@ def build_summary(
f"{verifier['ledger_policy_depth_limit_exceeded']}"
),
f"- Verifier models: {_format_counter(verifier['verifier_models'])}",
f"- Verifier CLI versions: {_format_counter(verifier['verifier_cli_versions'])}",
f"- Unsupported verifier models: {_format_counter(verifier['unsupported_verifier_models'])}",
(
"- Unsupported model dispositions: "
Expand All @@ -1034,6 +1101,25 @@ def build_summary(
),
f"- Model selection reasons: {_format_counter(verifier['model_selection_reasons'])}",
f"- Verifier modes: {_format_counter(verifier['verifier_modes'])}",
"",
"## Codex CLI Freshness",
f"- Records: {codex_cli_freshness['records']}",
f"- Statuses: {_format_counter(codex_cli_freshness['statuses'])}",
f"- Packages: {_format_counter(codex_cli_freshness['packages'])}",
f"- Pinned versions: {_format_counter(codex_cli_freshness['pinned_versions'])}",
f"- Latest versions: {_format_counter(codex_cli_freshness['latest_versions'])}",
f"- Outdated records: {codex_cli_freshness['outdated_records']}",
(
"- Latest unavailable records: "
f"{codex_cli_freshness['latest_unavailable_records']}"
),
(
"- Max version delta: "
f"major {codex_cli_freshness['max_version_delta']['major']}, "
f"minor {codex_cli_freshness['max_version_delta']['minor']}, "
f"patch {codex_cli_freshness['max_version_delta']['patch']}"
),
f"- Update targets: {_format_counter(codex_cli_freshness['update_targets'])}",
]
)

Expand Down
Loading