#include "goal.h" #include #include #include static const char *const GOAL_PREAMBLE = "You are the reviewer for a terminal coding agent that is working toward a " "goal. You are given the goal — a success condition — and the agent's most " "recent reply. Decide whether the goal is fully met.\n" "\n" "Reply with exactly two lines:\n" "1. Either `MET` or `NOT MET` — your verdict.\n" "2. One short sentence: if met, what was accomplished; if not, the single " "most important gap to fix next.\n" "\n" "Be strict: the goal is met only when the stated success condition is " "clearly satisfied — not merely started, in progress, or partially done. " "Do not give the agent the benefit of the doubt, and do not add commentary " "beyond the two lines."; void goal_spec_free(goal_spec *s) { if (!s) return; free(s->criteria); s->criteria = NULL; } void goal_report_free(goal_report *r) { if (!r) return; free(r->criteria); free(r); } const char *goal_evaluator_preamble(void) { return GOAL_PREAMBLE; } static bool str_eq_i(const char *s, size_t n, const char *word) { size_t wn = strlen(word); if (n != wn) return false; for (size_t i = 0; i < wn; i++) { char a = s[i], b = word[i]; if (a >= 'a' && a <= 'z') a = (char)(a - 'a' + 'A'); if (b >= 'a' && b <= 'z') b = (char)(b - 'a' + 'A'); if (a != b) return false; } return true; } static int parse_tries(const char *s, size_t n, unsigned long *out, char **err) { size_t tlen; const char *t = xtrim_span(s, n, &tlen); size_t digits = 0; while (digits < tlen && t[digits] >= '0' && t[digits] <= '9') digits++; if (digits == 0) { *err = xstrdup( "expected a number of tries after `stop after`, e.g. " "`stop after 5 tries`"); return 0; } /* After the digits: end of input, or the word `tries` as a whole word. * "5xyz" is rejected; "5 tries" is the canonical form. */ size_t rest = digits; while (rest < tlen && isspace((unsigned char)t[rest])) rest++; if (rest != tlen) { size_t wn = 5; bool word_ok = (tlen - rest) >= wn; if (word_ok) { const char *w = "tries"; for (size_t i = 0; i < wn; i++) { char a = t[rest + i], b = w[i]; if (a >= 'a' && a <= 'z') a = (char)(a - 'a' + 'A'); if (b >= 'a' && b <= 'z') b = (char)(b - 'a' + 'A'); if (a != b) { word_ok = false; break; } } } if (!word_ok || ((tlen - rest) > wn && isalpha((unsigned char)t[rest + wn]))) { *err = xstrdup( "expected a number of tries after `stop after`, e.g. " "`stop after 5 tries`"); return 0; } } unsigned long value = 0; for (size_t i = 0; i < digits; i++) { value = value * 10UL + (unsigned long)(t[i] - '0'); if (value > 4294967295UL) { *err = xstrdup( "expected a number of tries after `stop after`, e.g. " "`stop after 5 tries`"); return 0; } } if (value == 0) { *err = xstrdup("the goal needs at least 1 try"); return 0; } *out = value; return 1; } int goal_parse(const char *input, goal_spec *out, char **err) { /* Zero on entry so a failed parse leaves a freeable, empty spec. */ memset(out, 0, sizeof *out); const char *src = input ? input : ""; size_t slen = strlen(src); size_t tlen; const char *t = xtrim_span(src, slen, &tlen); if (tlen == 0) { *err = xstrdup( "give the goal its success criteria, e.g. " "`/goal all tests pass, stop after 5 tries`"); return 0; } const char marker[] = "stop after"; size_t mlen = sizeof(marker) - 1; /* rfind: last occurrence of marker in [t, t+tlen). */ const char *hit = NULL; const char *p = t; while (tlen - (size_t)(p - t) >= mlen) { void *m = memmem(p, tlen - (size_t)(p - t), marker, mlen); if (!m) break; hit = (const char* ) m; p = (const char *)m + 1; } if (!hit) { out->criteria = xstrndup(t, tlen); out->max_tries = GOAL_DEFAULT_MAX_TRIES; return 1; } size_t pre_len = (size_t)(hit - t); size_t clen; const char *crit = xtrim_span(t, pre_len, &clen); while (clen > 0 && crit[clen - 1] == ',') clen--; crit = xtrim_span(crit, clen, &clen); if (clen == 0) { *err = xstrdup( "give the goal its success criteria before `stop after`, e.g. " "`/goal all tests pass, stop after 5 tries`"); return 0; } const char *tail = hit + mlen; if (!parse_tries(tail, tlen - (size_t)(tail - t), &out->max_tries, err)) return 0; out->criteria = xstrndup(crit, clen); return 1; } char *goal_prompt(const goal_spec *spec, unsigned long attempt, const char *feedback) { const char *crit = spec->criteria ? spec->criteria : ""; if (attempt <= 1) return xstrfmt( "Work toward this goal until it is fully met:\n\n%s\n\n" "Judge your own progress, verify it, and keep going if it is not " "done. When you are confident the goal is fully met, stop and say " "what you did.", crit); const char *fb = feedback ? feedback : "no specific feedback was given"; return xstrfmt( "You are on attempt %lu of %lu toward this goal:\n\n%s\n\n" "A reviewer judged the goal NOT yet met: %s\n\n" "Address that gap and keep working. When you are confident the goal is " "fully met, stop and say what you did.", attempt, spec->max_tries, crit, fb); } /* True if `needle` occurs in `hay` as a whole word (A-Z only; `hay` is * already upper-cased). Prevents "MET" matching inside "UNMET". */ static bool has_word(const char *hay, const char *needle) { size_t nl = strlen(needle); const char *p = hay; while ((p = strstr(p, needle)) != NULL) { bool left_ok = (p == hay) || (p[-1] < 'A' || p[-1] > 'Z'); bool right_ok = (p[nl] < 'A' || p[nl] > 'Z'); if (left_ok && right_ok) return true; p += (nl > 0 ? nl : 1); } return false; } void goal_parse_verdict(const char *reply, goal_verdict *out) { const char *src = reply ? reply : ""; char *upper = xstrdup(src); for (char *c = upper; *c; c++) if (*c >= 'a' && *c <= 'z') *c = (char)(*c - 'a' + 'A'); out->met = has_word(upper, "MET") && !has_word(upper, "NOT MET") && !has_word(upper, "UNMET"); free(upper); const char *chosen = NULL; size_t chosen_len = 0; const char *p = src; while (*p) { const char *nl = strchr(p, '\n'); size_t linelen = nl ? (size_t)(nl - p) : strlen(p); size_t tlen; const char *line = xtrim_span(p, linelen, &tlen); if (tlen > 0 && !str_eq_i(line, tlen, "MET") && !str_eq_i(line, tlen, "NOT MET")) { chosen = line; chosen_len = tlen; break; } if (!nl) break; p = nl + 1; } out->reason = chosen ? xstrndup(chosen, chosen_len) : xstrdup("the reviewer did not give a reason"); }