| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | ) |
| 6 | |
| 7 | // taskFaultSignals are CN/EN failure/fault phrases shared by task recognition |
| 8 | // and Goal budget classification so the two surfaces cannot drift apart. |
| 9 | var taskFaultSignals = []string{ |
| 10 | "not working", "isn't working", "doesn't work", "dont work", "don't work", |
| 11 | "broken", "error", "bug", "issue", "failed", "failing", "crash", "exception", "panic", "segfault", |
| 12 | "问题", "不工作", "报错", "错误", "失败", "崩溃", "异常", |
| 13 | "卡住", "卡住了", "没反应", "不生效", "异常退出", "历史 bug", "历史bug", |
| 14 | } |
| 15 | |
| 16 | // taskFaultReadonlySignals mark diagnosis/analysis-only work. A bare fault |
| 17 | // statement without these (and without advisory/no-modify constraints) is |
| 18 | // treated as a Goal write budget. |
| 19 | var taskFaultReadonlySignals = []string{ |
| 20 | "diagnose", "diagnosis", "analyze", "analyse", "inspect", "review", "reproduce", |
| 21 | "identify the root cause", "root cause", "investigate", "audit", "verify", "check why", |
| 22 | "诊断", "排查", "定位", "分析", "检查", "复现", "评审", "审查", "审计", "验证", |
| 23 | "原因", "根因", |
| 24 | } |
| 25 | |
| 26 | // GoalTaskNeedsWriteBudget reports whether a Goal objective should start on the |
| 27 | // extended write turn budget. It preserves every explicit mutation classification used by |
| 28 | // ordinary Delivery, then additionally treats bare fault/bug/crash statements as |
| 29 | // write work unless the user asked only for analysis, explanation, or a no-modify |
| 30 | // constraint. Ordinary Delivery evidence/mutation gates keep using |
| 31 | // deliveryTaskNeedsMutation / classifyDeliveryTaskIntent unchanged. |
| 32 | func GoalTaskNeedsWriteBudget(input string) bool { |
| 33 | if TaskNeedsMutation(input) { |
| 34 | return true |
| 35 | } |
| 36 | return goalBareFaultImpliesWrite(input) |
| 37 | } |
| 38 | |
| 39 | func goalBareFaultImpliesWrite(input string) bool { |
| 40 | normalized := strings.ToLower(strings.TrimSpace(input)) |
| 41 | if normalized == "" || !taskInputHasFaultSignal(normalized) { |
| 42 | return false |
| 43 | } |
| 44 | // Explicit "do not modify/fix" (or equivalent) keeps the goal on simple. |
| 45 | if goalHasNoModifyConstraint(normalized) { |
| 46 | return false |
| 47 | } |
| 48 | // Questions and explanation intent stay consultative. |
| 49 | if deliveryTaskIsAdvisory(normalized) || goalHasQuestionIntent(normalized) { |
| 50 | return false |
| 51 | } |
| 52 | // Explicit read-only diagnostic wording stays on the simple budget even when |
| 53 | // it mentions a bug/crash/failure. |
| 54 | if goalHasReadonlyDiagnosticIntent(normalized) { |
| 55 | return false |
| 56 | } |
| 57 | return true |
| 58 | } |
| 59 | |
| 60 | func taskInputHasFaultSignal(input string) bool { |
| 61 | normalized := strings.ToLower(strings.TrimSpace(input)) |
| 62 | for _, phrase := range taskFaultSignals { |
| 63 | if strings.Contains(normalized, phrase) { |
| 64 | return true |
| 65 | } |
| 66 | } |
| 67 | return false |
| 68 | } |
| 69 | |
| 70 | func goalHasQuestionIntent(input string) bool { |
| 71 | if strings.ContainsAny(input, "??") { |
| 72 | return true |
| 73 | } |
| 74 | // Explanation/consultative stems beyond the shared advisory phrase list. |
| 75 | for _, phrase := range []string{ |
| 76 | "explain", "what happened", "what's happening", "what is happening", |
| 77 | "tell me about", "look into why", |
| 78 | "解释", "说明一下", "怎么回事", "是什么情况", "什么情况", |
| 79 | } { |
| 80 | if strings.Contains(input, phrase) { |
| 81 | return true |
| 82 | } |
| 83 | } |
| 84 | return false |
| 85 | } |
| 86 | |
| 87 | func goalHasReadonlyDiagnosticIntent(input string) bool { |
| 88 | for _, phrase := range taskFaultReadonlySignals { |
| 89 | if strings.Contains(input, phrase) { |
| 90 | return true |
| 91 | } |
| 92 | } |
| 93 | return deliveryTaskIsAdvisory(input) |
| 94 | } |
| 95 | |
| 96 | func goalHasNoModifyConstraint(input string) bool { |
| 97 | if _, negated := deliveryTaskMutationIntent(input); negated && !deliveryTaskHasMutationIntent(input) { |
| 98 | return true |
| 99 | } |
| 100 | for _, phrase := range []string{ |
| 101 | "do not fix", "don't fix", "dont fix", "do not change", "don't change", "dont change", |
| 102 | "do not modify", "don't modify", "dont modify", "do not edit", "don't edit", |
| 103 | "without fixing", "without changing", "without modifying", "analysis only", "review only", |
| 104 | "不要修复", "不要修改", "不要改动", "不要改", "别修复", "别修改", "勿修改", |
| 105 | "只分析", "仅分析", "只检查", "仅检查", "只诊断", "仅诊断", "只定位", "仅定位", |
| 106 | "只排查", "仅排查", "不要修", |
| 107 | } { |
| 108 | if strings.Contains(input, phrase) { |
| 109 | return true |
| 110 | } |
| 111 | } |
| 112 | // Trailing constraints after punctuation, e.g. "复现并定位问题,但不要修复。" |
| 113 | for _, clause := range deliveryTaskClauses(input) { |
| 114 | clause = strings.TrimSpace(clause) |
| 115 | if clause == "" { |
| 116 | continue |
| 117 | } |
| 118 | for _, phrase := range []string{"不要修复", "不要修改", "do not fix", "don't fix", "dont fix"} { |
| 119 | if strings.Contains(clause, phrase) { |
| 120 | return true |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | return false |
| 125 | } |
| 126 |