You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(test-classifier): post + record true negatives; add --simulate; steer observed mode to narrow targets
Three changes on top of the current dispatcher, from pilot feedback:
- Post AND record true negatives. A NO_ACTION result now posts a "no action
required" PR comment (carrying the 👍/👎 ask) instead of staying silent, and
auto-submit/--submit writes a Testing Events row for it (verdict NO_ACTION).
Previously a true negative was invisible to both the PR and the metrics sheet,
so the tuning loop never saw "agent ran, nothing to triage" outcomes.
- Add --simulate: skip the agent and feed synthetic output through the real
posting/metrics path, so the comment + reaction + Testing Events pipeline can
be validated end-to-end without a live (slow/costly) agent run. Set
AI_SIMULATE_RESULT=NO_ACTION to exercise the true-negative path.
- Steer OBSERVED mode to run the narrowest test target (changed module/package)
rather than a full multi-module build, to avoid the reactor-build timeouts
seen on large repos.
Also ignore test-repos/ (cloned fixtures for exercising the classifier).
# Synthesize a classifier output for --simulate mode, byte-compatible with what
1054
+
# a real agent emits: the fenced JSON block plus the trailing result marker. This
1055
+
# lets the full downstream path (parse_result → extract_classifier_json →
1056
+
# render_pr_comment_body → post_comment_to_github → submit_metrics_row) run
1057
+
# without invoking the agent. Set AI_SIMULATE_RESULT=NO_ACTION to exercise the
1058
+
# true-negative comment + row instead of the default CLASSIFIED table.
1059
+
ai_review::synthetic_output() {
1060
+
local kind="${AI_SIMULATE_RESULT:-CLASSIFIED}"
1061
+
local mode="INFERRED"
1062
+
(( AI_RUN_SUITE ==1))&& mode="OBSERVED"
1063
+
1064
+
if [[ "${kind}"=="NO_ACTION" ]];then
1065
+
cat <<SIM
1066
+
[SIMULATED OUTPUT — no agent was invoked (--simulate).]
1067
+
1068
+
<!-- AI_CLASSIFIER_JSON_BEGIN -->
1069
+
{ "mode": "${mode}", "summary": "SIMULATED: no failing tests were triaged (pipeline dry run).", "classifications": [] }
1070
+
<!-- AI_CLASSIFIER_JSON_END -->
1071
+
1072
+
<<<AI_REVIEW_RESULT:NO_ACTION>>>
1073
+
SIM
1074
+
else
1075
+
cat <<SIM
1076
+
[SIMULATED OUTPUT — no agent was invoked (--simulate).]
1077
+
1078
+
<!-- AI_CLASSIFIER_JSON_BEGIN -->
1079
+
{ "mode": "${mode}", "summary": "SIMULATED: filler triage for pipeline dry run (these verdicts are not real).", "classifications": [ { "verdict": "FLAKY_FAILURE", "test": "simulated::filler_test", "path": "SIMULATED", "line": 0, "category": "other", "confidence": "low", "in_scope": true, "rationale": "Synthetic entry produced by --simulate to exercise the posting/metrics path without a live agent run." } ] }
1080
+
<!-- AI_CLASSIFIER_JSON_END -->
1081
+
1082
+
<<<AI_REVIEW_RESULT:CLASSIFIED>>>
1083
+
SIM
1084
+
fi
1085
+
}
1086
+
996
1087
# ── Custom run loop (mirrors the security PR dispatcher) ────────────────────
997
1088
test_classifier::run() {
998
1089
# Discover the PR (and inject --against into REMAINING_FOR_LIB).
@@ -1006,12 +1097,15 @@ test_classifier::run() {
1006
1097
exit 0
1007
1098
fi
1008
1099
1009
-
ai_review::resolve_tool
1100
+
# --simulate skips the agent, so it does not need a resolved/installed AI CLI.
1101
+
if(( SIMULATE ==0));then
1102
+
ai_review::resolve_tool
1103
+
fi
1010
1104
1011
1105
if(( AI_REVIEW_DRY_RUN ==1));then
1012
1106
ai_review::info "DRY-RUN — no AI invocation will be made."
ai_review::info "SIMULATE — skipping the AI agent; feeding synthetic output (${AI_SIMULATE_RESULT:-CLASSIFIED}) through the real posting/metrics path."
# ── Post ONE PR comment with the verdicts. ────────────────────────────────
1117
1216
# --post-comment is what CI passes to actually post; omit it for a local dry
1118
-
# view (the JSON/report still prints to stdout). Nothing is posted when nothing
1119
-
# was triaged (NO_ACTION). The comment carries the 👍/👎 reaction ask on a
1120
-
# CI/--post-comment run; when we capture the signal via the terminal prompt
1121
-
# (--submit or auto-submit) the ask is omitted (the prompt is the signal).
1217
+
# view (the JSON/report still prints to stdout). A comment is posted on BOTH
1218
+
# results: CLASSIFIED (the verdict table) AND NO_ACTION (a "no action required"
1219
+
# comment). The true negative is posted so its 👍/👎 reaction can be harvested
1220
+
# by the metrics loop — suppressing it (the earlier behavior) made true
1221
+
# negatives invisible to tuning. render_pr_comment_body renders a dedicated
1222
+
# "no action required" body when classifications is empty. The comment carries
1223
+
# the 👍/👎 reaction ask on a CI/--post-comment run; when we capture the signal
1224
+
# via the terminal prompt (--submit or auto-submit) the ask is omitted.
1122
1225
if(( POST_COMMENT ==1));then
1123
-
if [[ "${result}"=="NO_ACTION" ]];then
1124
-
ai_review::info "Result is NO_ACTION — nothing to triage, so no PR comment is posted."
1125
-
else
1126
-
if [[ -z"${AI_REVIEW_PR_NUMBER:-}" ]];then
1127
-
ai_review::err "--post-comment requires a discoverable PR. Use --pr <number> or ensure 'gh pr view' resolves. (A --unpushed local run has no PR, so it is report-only — drop --post-comment.)"
ai_review::err "--post-comment requires a discoverable PR. Use --pr <number> or ensure 'gh pr view' resolves. (A --unpushed local run has no PR, so it is report-only — drop --post-comment.)"
0 commit comments