open-multi-agent-kit 0.90.6 → 0.90.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +9 -9
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/command-safety.d.ts.map +1 -1
  7. package/dist/core/command-safety.js +7 -0
  8. package/dist/core/command-safety.js.map +1 -1
  9. package/dist/core/reasoning-router-v4-normalize.d.ts +11 -0
  10. package/dist/core/reasoning-router-v4-normalize.d.ts.map +1 -0
  11. package/dist/core/reasoning-router-v4-normalize.js +224 -0
  12. package/dist/core/reasoning-router-v4-normalize.js.map +1 -0
  13. package/dist/core/reasoning-router-v4-weights.d.ts +13 -5
  14. package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
  15. package/dist/core/reasoning-router-v4-weights.js +8 -4
  16. package/dist/core/reasoning-router-v4-weights.js.map +1 -1
  17. package/dist/core/reasoning-router-v4.d.ts +3 -3
  18. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  19. package/dist/core/reasoning-router-v4.js +394 -58
  20. package/dist/core/reasoning-router-v4.js.map +1 -1
  21. package/dist/core/router-feedback-collector.d.ts +1 -1
  22. package/dist/core/router-feedback-collector.d.ts.map +1 -1
  23. package/dist/core/router-feedback-collector.js +9 -1
  24. package/dist/core/router-feedback-collector.js.map +1 -1
  25. package/dist/core/settings-manager.d.ts +3 -3
  26. package/dist/core/settings-manager.d.ts.map +1 -1
  27. package/dist/core/settings-manager.js.map +1 -1
  28. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  29. package/dist/modes/interactive/components/settings-selector.js +1 -0
  30. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  31. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  32. package/dist/modes/interactive/components/thinking-selector.js +1 -0
  33. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  34. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  35. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  36. package/dist/modes/interactive/theme/theme.js +2 -0
  37. package/dist/modes/interactive/theme/theme.js.map +1 -1
  38. package/docs/adaptorch-preview.md +84 -0
  39. package/docs/loadout-domains/frontend-ui.md +5 -1
  40. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  41. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  42. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  43. package/examples/extensions/gondolin/package-lock.json +2 -2
  44. package/examples/extensions/gondolin/package.json +1 -1
  45. package/examples/extensions/preset.ts +2 -2
  46. package/examples/extensions/sandbox/package-lock.json +2 -2
  47. package/examples/extensions/sandbox/package.json +1 -1
  48. package/examples/extensions/with-deps/package-lock.json +2 -2
  49. package/examples/extensions/with-deps/package.json +1 -1
  50. package/npm-shrinkwrap.json +12 -12
  51. package/package.json +4 -4
@@ -22,6 +22,7 @@
22
22
  import { DEFAULT_WEIGHTS_V4, TASK_CLASSES_V4, } from "./reasoning-router-v4-weights.js";
23
23
  export { DEFAULT_WEIGHTS_V4, TASK_CLASSES_V4 } from "./reasoning-router-v4-weights.js";
24
24
  import { resolveThinkingLevelCore, TASK_CLASS_THINKING_LEVELS } from "./reasoning-router-resolver.js";
25
+ import { extractGeneralizedIntentEvidenceV4 } from "./reasoning-router-v4-normalize.js";
25
26
  /** Prompts shorter than this (trimmed) fall back to trivial when no real signal scores > 0. */
26
27
  const TRIVIAL_MAX_CHARS_V4 = 40;
27
28
  /** Prompts at/above this length with no real signal fall back to plan (long prose brief). */
@@ -43,13 +44,234 @@ const LANE_FALLBACK_CLASS_V4 = {
43
44
  reviewer: "review",
44
45
  tester: "code-gen",
45
46
  };
47
+ const INTENT_LEXEME_CLUSTERS_V4 = [
48
+ {
49
+ id: "debug-leading-actions",
50
+ taskClass: "debug",
51
+ role: "leading-intent",
52
+ surfaces: ["debug", "investigate why", "reproduce", "trace", "fix this traceback/panic/error"],
53
+ phrases: [/^(?:debug|investigate\s+why|reproduce|trace\b)|^fix\s+this\s+(?:traceback|panic|error)\b/i],
54
+ negativeControlIds: ["negated-debug-action", "generic-error-handling"],
55
+ },
56
+ {
57
+ id: "review-leading-actions",
58
+ taskClass: "review",
59
+ role: "leading-intent",
60
+ surfaces: ["review", "audit", "critique", "inspect", "assess", "approve", "double-check", "lgtm"],
61
+ phrases: [/^(?:review|audit(?!\s+log\b)|critique|inspect|assess|approve|double-?check|lgtm)\b/i],
62
+ negativeControlIds: ["audit-log-not-review", "review-negated-before-code-gen"],
63
+ },
64
+ {
65
+ id: "review-leading-synonyms",
66
+ taskClass: "review",
67
+ role: "leading-intent",
68
+ surfaces: [
69
+ "give this PR a once-over",
70
+ "look over",
71
+ "sanity-check",
72
+ "eyeball",
73
+ "poke holes",
74
+ "pressure-test",
75
+ "tell me whether/if",
76
+ "what could go wrong",
77
+ ],
78
+ phrases: [
79
+ /^(?:give\s+(?:this|the|my)?\s*(?:pr|pull\s+request|diff|code|change|branch)?\s*(?:a\s+)?once-?over|look\s+over|sanity[-\s]?check|eyeball|poke\s+holes(?:\s+in)?|pressure[-\s]?test|sign\s+off|tell\s+me\s+(?:if|whether)|tell\s+me\s+what\s+could\s+go\s+wrong|where\s+would\b[^.!?;\n]{0,100}\bfall\s+apart|what\s+am\s+i\s+missing|check\s+whether|look\s+for\s+(?:edge\s+cases|risks?|flaws?)|find\s+(?:flaws|risks?|issues?)|validate\s+(?:the\s+)?reasoning|give\s+me\s+a\s+thumbs[-\s]?up\/?down|read\s+(?:this|the|my)\b[^.!?;\n]{0,80}\bchallenge|(?:is|are|does|do|can|could|would|should)\s+(?:this|these|my|the)\b[^.!?;\n]{0,100}\b(?:right\s+way|handle|cover|safe|sane|clean|overkill|prove|leak|ship|concurrent|hidden\s+coupling)|(?:at\s+architecture\s+level|architecture[-\s]?level)\b[^.!?;\n]{0,80}\bhidden\s+coupling)\b/i,
80
+ ],
81
+ negativeControlIds: ["sanity-check-setup-script", "future-self-review-context", "check-whether-implementation"],
82
+ },
83
+ {
84
+ id: "plan-leading-actions",
85
+ taskClass: "plan",
86
+ role: "leading-intent",
87
+ surfaces: ["plan", "design", "architect", "decompose", "map out", "draw up", "think through"],
88
+ phrases: [
89
+ /^(?:let(?:'|’)s\s+)?(?:plan|design|architect|decompose|map\s+out|draw\s+up|think\s+through)\b/i,
90
+ /^(?:write|create)\s+(?:a\s+)?(?:technical\s+)?(?:spec|roadmap|strategy|plan)\b/i,
91
+ ],
92
+ negativeControlIds: ["design-token-edit", "draw-up-implementation-only"],
93
+ },
94
+ {
95
+ id: "refactor-leading-actions",
96
+ taskClass: "refactor",
97
+ role: "leading-intent",
98
+ surfaces: ["refactor", "extract", "rename", "deduplicate", "simplify", "clean up", "untangle"],
99
+ phrases: [
100
+ /^(?:refactor(?:ing|ed)?|extract|rename|deduplicate|consolidate|modularize|reorganize|simplify|restructure|untangle)\b|^clean\s+up\b|^split\s+(?:the\s+)?module\b|^move\s+logic\b/i,
101
+ ],
102
+ negativeControlIds: ["negated-refactor", "future-self-refactor"],
103
+ },
104
+ {
105
+ id: "simple-edit-leading-actions",
106
+ taskClass: "simple-edit",
107
+ role: "leading-intent",
108
+ surfaces: ["correct", "update", "swap", "remove", "adjust", "fix", "add", "change", "trim", "reword"],
109
+ phrases: [/^(?:correct|update|swap|remove|adjust|fix|add|change|trim|reword|tweak|bump)\b/i],
110
+ negativeControlIds: ["add-feature-not-simple-edit", "fix-crash-not-simple-edit"],
111
+ },
112
+ {
113
+ id: "code-gen-leading-actions",
114
+ taskClass: "code-gen",
115
+ role: "leading-intent",
116
+ surfaces: [
117
+ "implement",
118
+ "write",
119
+ "create",
120
+ "build",
121
+ "generate",
122
+ "scaffold",
123
+ "prototype",
124
+ "add",
125
+ "whip up",
126
+ "cook me",
127
+ ],
128
+ phrases: [/^(?:implement|write|create|build|generate|scaffold|prototype|add|whip\s+up|cook\s+me)\b/i],
129
+ negativeControlIds: ["write-a-plan", "create-a-review"],
130
+ },
131
+ {
132
+ id: "debug-whole-prompt",
133
+ taskClass: "debug",
134
+ role: "whole-prompt",
135
+ surfaces: ["debug", "investigate why", "reproduce", "track down", "figure out why", "get to the bottom"],
136
+ phrases: [
137
+ /^(?:debug|investigate\s+why|reproduce|trace\b)|\bfix\s+this\s+(?:traceback|panic|error)\b|\btrack\s+down\b|\bfigure\s+out\s+why\b|\bget\s+to\s+the\s+bottom\b|디버깅|디버그|재현|원인\s*분석/i,
138
+ ],
139
+ negativeControlIds: ["not-a-bug-report", "error-handling-feature"],
140
+ },
141
+ {
142
+ id: "review-whole-prompt",
143
+ taskClass: "review",
144
+ role: "whole-prompt",
145
+ surfaces: [
146
+ "review",
147
+ "critique",
148
+ "assess",
149
+ "inspect",
150
+ "once-over",
151
+ "look over",
152
+ "sanity-check",
153
+ "eyeball",
154
+ "poke holes",
155
+ "pressure-test",
156
+ "tell me whether correct",
157
+ ],
158
+ phrases: [
159
+ /\b(review|critique|assess|inspect|approve|lgtm|double-?check|sanity[-\s]?check|once-?over|look\s+over|eyeball|poke\s+holes|pressure[-\s]?test|sign\s+off|thumbs[-\s]?up\/?down|what\s+could\s+go\s+wrong|what\s+am\s+i\s+missing|challenge\s+the\s+assumptions|hidden\s+coupling|tell\s+me\s+(?:if|whether)[^.!?;\n]{0,80}\bcorrect|audit(?!\s+log\b))\b|리뷰|검토|점검|괜찮은지\s*봐\s*줘|맞는지\s*봐\s*줘|처리되는지\s*확인|허점.{0,10}찾|문제\s*없는지/i,
160
+ ],
161
+ negativeControlIds: ["review-free-implementation", "audit-log"],
162
+ },
163
+ {
164
+ id: "plan-whole-prompt",
165
+ taskClass: "plan",
166
+ role: "whole-prompt",
167
+ surfaces: ["plan", "design", "architecture", "roadmap", "map out", "draw up", "think through"],
168
+ phrases: [
169
+ /\b(plan|design|architect|architecture|roadmap|spec(?:ification)?|strategy|decompose|milestones?|map\s+out|phases|draw\s+up|think\s+through|write\s+a\s+(?:technical\s+)?spec|create\s+a\s+(?:roadmap|strategy|plan))\b|설계|로드맵|아키텍처|계획.{0,6}세워|기획/i,
170
+ ],
171
+ negativeControlIds: ["rather-than-design", "design-token-edit"],
172
+ },
173
+ {
174
+ id: "code-gen-whole-prompt",
175
+ taskClass: "code-gen",
176
+ role: "whole-prompt",
177
+ surfaces: ["implement", "write", "create", "build", "generate", "scaffold", "prototype", "whip up", "cook me"],
178
+ phrases: [
179
+ /\b(implement|write|create|build|generate|scaffold|prototype|whip\s+up|cook\s+me)\b|구현|만들어\s*줘|생성|작성|추가해/i,
180
+ ],
181
+ negativeControlIds: ["write-plan", "create-roadmap"],
182
+ },
183
+ {
184
+ id: "code-gen-artifact-request",
185
+ taskClass: "code-gen",
186
+ role: "object-shape",
187
+ surfaces: ["I need a script", "we need a utility", "I need an endpoint", "we need a test"],
188
+ phrases: [
189
+ /\b(?:i|we)\s+need\s+(?:a|an|the|some)?\s*(?:small\s+|quick\s+|new\s+)?(?:script|utility|helper|component|endpoint|migration|test|tool|function|service)\b(?!\s+(?:plan|strategy|review|audit|roadmap|spec))/i,
190
+ ],
191
+ negativeControlIds: ["need-a-plan", "need-a-review", "need-help-debugging"],
192
+ },
193
+ {
194
+ id: "evaluative-review-object",
195
+ taskClass: "review",
196
+ role: "object-shape",
197
+ surfaces: ["holes", "looks off", "correct", "safe", "risky", "issues", "regressions"],
198
+ phrases: [/\b(holes?|looks?\s+off|correct|safe|risky|issues?|regressions?)\b/i],
199
+ negativeControlIds: ["fix-holes", "implement-issue-fix"],
200
+ },
201
+ {
202
+ id: "korean-debug-morphology",
203
+ taskClass: "debug",
204
+ role: "korean-morphology",
205
+ surfaces: [
206
+ "오류",
207
+ "에러",
208
+ "실패",
209
+ "원인 찾아줘",
210
+ "원인 파악",
211
+ "숫자가 이상해",
212
+ "합계가 안 맞아",
213
+ "빈 파일",
214
+ "CI가 빨개",
215
+ "디버깅",
216
+ ],
217
+ phrases: [
218
+ /오류|에러|실패|안\s*돼|안\s*됨|깨졌|깨짐|고장|디버깅|디버그|재현|원인\s*(?:분석|파악|찾(?:아|기)?|찾아\s*줘)?|숫자.{0,12}(?:이상|틀려|안\s*맞)|합계.{0,12}(?:안\s*맞|틀려|이상)|빈\s*파일|(?:CI|러너).{0,12}빨개/i,
219
+ ],
220
+ negativeControlIds: ["korean-error-handling-feature", "korean-greeting"],
221
+ },
222
+ {
223
+ id: "korean-simple-edit-morphology",
224
+ taskClass: "simple-edit",
225
+ role: "korean-morphology",
226
+ surfaces: ["오타", "맞춤법", "띄어쓰기", "문구", "제목"],
227
+ phrases: [/오타|맞춤법|띄어쓰기|문구|제목/i],
228
+ negativeControlIds: ["korean-feature-edit"],
229
+ },
230
+ {
231
+ id: "korean-review-morphology",
232
+ taskClass: "review",
233
+ role: "korean-morphology",
234
+ surfaces: ["리뷰", "검토", "점검", "괜찮은지 봐줘", "맞는지 봐줘", "허점 찾아줘"],
235
+ phrases: [/리뷰|검토|점검|괜찮은지\s*봐\s*줘|맞는지\s*봐\s*줘|처리되는지\s*확인|허점.{0,10}찾|문제\s*없는지/i],
236
+ negativeControlIds: ["korean-review-negated"],
237
+ },
238
+ {
239
+ id: "korean-plan-morphology",
240
+ taskClass: "plan",
241
+ role: "korean-morphology",
242
+ surfaces: ["설계", "로드맵", "아키텍처", "계획 세워", "계획 짜", "계획 수립", "계획 정리"],
243
+ phrases: [/설계|로드맵|아키텍처|계획.{0,8}(?:세워|짜|수립|정리)|기획/i],
244
+ negativeControlIds: ["korean-plan-negated"],
245
+ },
246
+ {
247
+ id: "korean-refactor-morphology",
248
+ taskClass: "refactor",
249
+ role: "korean-morphology",
250
+ surfaces: ["리팩토링", "리팩터링", "구조 개선"],
251
+ phrases: [/리팩토링|리팩터링|구조\s*개선/i],
252
+ negativeControlIds: ["korean-refactor-negated"],
253
+ },
254
+ {
255
+ id: "korean-code-gen-morphology",
256
+ taskClass: "code-gen",
257
+ role: "korean-morphology",
258
+ surfaces: ["구현", "만들", "생성", "작성", "추가", "테스트", "수정", "변경", "바꿔"],
259
+ phrases: [/구현|만들|생성|작성|추가|테스트|삭제|지워|지우|제거|고쳐|고치|수정|변경|바꿔|바꾸|옮겨|옮기|넣어/i],
260
+ negativeControlIds: ["korean-greeting", "korean-codegen-negated"],
261
+ },
262
+ ];
263
+ function clusterMatchesRoleV4(text, taskClass, role) {
264
+ return INTENT_LEXEME_CLUSTERS_V4.some((cluster) => cluster.taskClass === taskClass &&
265
+ cluster.role === role &&
266
+ cluster.phrases.some((pattern) => pattern.test(text)));
267
+ }
46
268
  // ============================================================================
47
269
  // Whole-prompt / leading-clause patterns used by the v4 scorer.
48
270
  // ============================================================================
49
271
  const LOCAL_EDIT_OBJECT_PATTERN = /\b(spelling|grammar|capitalization|date\s+format|author\s+e-?mail|copyright\s+year|headline|title|tooltip|placeholder|punctuation|comma|period|semicolon|closing\s+html\s+tag|closing\s+tag|double\s+space|whitespace|indentation|table\s+alignment|typos?|one-?liner?|single\s+line|sentence|stray|trailing)\b|오타|맞춤법|띄어쓰기|문구|제목/i;
50
272
  const LOCAL_EDIT_ACTION_OBJECT_PATTERN = /\b(update|change|swap|remove|correct|fix|add|adjust|trim)\s+(?:the\s+|a\s+|an\s+)?(?:missing\s+|stray\s+|author\s+|two\s+)?(?:e-?mail|comma|period|semicolon|tag|word|words|headline|title|copyright|spelling|grammar)\b/i;
51
273
  const IMPLEMENTATION_OBJECT_PATTERN = /\b(error\s+handling|input\s+validation|validation|auth(?:entication)?|retry|endpoint|migration|component|function|utility|helper|module|service|database|schema|column|middleware|rate\s+limiter|cache|caching|oauth|jwt|webhook)\b|에러\s*처리|오류\s*처리|입력\s*검증|인증|재시도|엔드포인트|마이그레이션|컴포넌트|함수|유틸|헬퍼|모듈|서비스|데이터베이스|스키마|미들웨어|레이트\s*리미터|캐시|웹훅|기능/i;
52
- const HARD_DIAGNOSTIC_PATTERN = /\b(stack\s*trace|traceback|panic|segfault|crash(?:es|ing|ed)?|throws?|fails?|failing|flaky|hangs?|timeout|500|EADDRINUSE|silently\s+fails?)\b|\b(?:TypeError|ReferenceError|RangeError|Error):|크래시|스택\s*트레이스|세그폴트|먹통|멈춰|타임아웃/i;
274
+ const HARD_DIAGNOSTIC_PATTERN = /\b(stack\s*trace|traceback|panic|segfault|crash(?:es|ing|ed)?|throws?|fails?|failing|flaky|hangs?|timeout|500|EADDRINUSE|silently\s+(?:fails?|produces?|returns?|creates?)|(?:ci|build)\s+(?:is\s+)?red|runner\s+(?:goes|went|is)\s+red|comes?\s+out\s+wrong|totals?\s+(?:are\s+)?off|drift(?:ed|ing)?|mismatch(?:ed|es)?|mismatched|no\s+longer\s+matches|says\s+success\s+but|exits?\s+0\s+but|empty\s+file|opens?\s+blank|black\s+image|zero[-\s]?byte\s+files?|headers\s+only|rows?\s+are\s+missing|missing\s+rows|stale\s+(?:index|prices?|cache|results?)|search\s+index\s+(?:is\s+)?stale|duplicate\s+rows|NaN|never\s+(?:send|sends|appear|appears|land|lands|writes?|written|updates?)|works\s+locally\s+and\s+breaks|worked\s+yesterday|settings\s+revert|side\s+effect\s+never|stops?\s+updating|data\s+disappears)\b|\b(?:TypeError|ReferenceError|RangeError|Error):|크래시|스택\s*트레이스|세그폴트|먹통|멈춰|타임아웃|숫자.{0,12}(?:이상|틀려|안\s*맞)|합계.{0,12}(?:안\s*맞|틀려|이상)|빈\s*파일|(?:CI|러너).{0,12}빨개/i;
53
275
  const BUG_OBJECT_PATTERN = /\b(null\s+pointer|null\s+deref|race(?:\s+condition)?|heap(?:\s+overflow)?|use-after-free|memory\s+leak|leaks?|deadlock|data\s+corruption|stale\s+data|off-by-one|encoding\s+bug|regression\s+(?:was\s+)?introduced|exceptions?|assertion\s+error|bugs?)\b|버그|예외|메모리\s*누수|데드락|무한\s*루프/i;
54
276
  const NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN = /\berror\s+(?:handling|messages?|budgets?)\b|에러\s*처리|오류\s*처리/i;
55
277
  const GENERIC_DIAGNOSTIC_PATTERN = /\b(errors?|broken|wrong\s+results|rolls?\s+back|rollback)\b/i;
@@ -67,21 +289,10 @@ const OPERATIONAL_RUNBOOK_SIGNAL_PATTERNS = [
67
289
  { pattern: /\bci\s*\/\s*cd\b|\bgithub\s+actions?\b|\bworkflow\b|깃허브\s*액션|깃헙\s*액션/i, critical: false },
68
290
  ];
69
291
  const EXPLICIT_RELEASE_RUNBOOK_PATTERN = /\bnpm\s+publish\b|\brelease\b[^\n]{0,80}\bv?\d+\.\d+\.\d+\b|릴리즈[^\n]{0,80}\d+\.\d+\.\d+/i;
70
- const REVIEW_SCOPE_PATTERN = /\b(pr|pull\s+request|diff|codebase|strategy|plan|spec|schema|design|api|coverage|security\s+posture|licensing|risks?|edge\s+cases|clarity|consistency|dependencies|third-party|ci\s+pipeline|threat\s+model|retry\s+logic|error\s+handling\s+strategy|error\s+messages?)\b/i;
292
+ const REVIEW_SCOPE_PATTERN = /\b(pr|pull\s+request|diff|codebase|approach|structure|module\s+boundary|locking\s+strategy|abstraction|assumptions?|parser\s+change|rollback\s+strategy|concurrent\s+case|concurrency\s+plan|hidden\s+coupling|failure\s+mode|test\s+quality|implementation\s+details|strategy|plan|spec|schema|design|api|coverage|security\s+posture|licensing|risks?|edge\s+cases|clarity|consistency|dependencies|third-party|ci\s+pipeline|threat\s+model|retry\s+logic|error\s+handling\s+strategy|error\s+messages?)\b/i;
71
293
  const REFACTOR_CUE_PATTERN = /\b(refactor(?:ing|ed)?|extract|rename|deduplicate|consolidate|modularize|reorganize|simplify|clean\s*up|tidy\s+up|restructure|split\s+module|move\s+logic|untangle|merge\s+duplicate)\b|리팩토링(?!하지\s*마|하지\s*말)|리팩터링(?!하지\s*마|하지\s*말)|구조\s*개선/i;
72
294
  const ADD_KEYWORD_PATTERN = /\badd\b/i;
73
295
  const LOW_RISK_EDIT_ACTION_PATTERN = /^(?:correct|update|swap|remove|reword|tweak|bump|trim)\b/i;
74
- /** Whole-prompt keyword-family patterns; refactor/simple-edit reuse the patterns above. */
75
- const DEBUG_KEYWORD_FAMILY_PATTERN = /^(?:debug|investigate\s+why|reproduce|trace\b)|\bfix\s+this\s+(?:traceback|panic|error)\b|\btrack\s+down\b|디버깅|디버그|재현|원인\s*분석/i;
76
- const REVIEW_KEYWORD_FAMILY_PATTERN = /\b(review|critique|assess|inspect|approve|lgtm|double-?check|sanity[-\s]?check|once-?over|eyeball|tell\s+me\s+(?:if|whether)[^.!?;\n]{0,80}\bcorrect|audit(?!\s+log\b))\b|리뷰|검토|점검/i;
77
- const PLAN_KEYWORD_FAMILY_PATTERN = /\b(plan|design|architect|architecture|roadmap|spec(?:ification)?|strategy|decompose|milestones?|map\s+out|phases|write\s+a\s+(?:technical\s+)?spec|create\s+a\s+(?:roadmap|strategy|plan))\b|설계|로드맵|아키텍처|계획.{0,6}세워|기획/i;
78
- const CODE_GEN_KEYWORD_FAMILY_PATTERN = /\b(implement|write|create|build|generate|scaffold|prototype)\b|구현|만들어\s*줘|생성|작성|추가해/i;
79
- const KO_SHORT_DEBUG_SIGNAL_PATTERN_V4 = /오류|에러|실패|안\s*돼|안\s*됨|깨졌|깨짐|고장|디버깅|디버그|재현|원인\s*(?:분석|파악)?/i;
80
- const KO_SHORT_SIMPLE_EDIT_SIGNAL_PATTERN_V4 = /오타|맞춤법|띄어쓰기|문구|제목/i;
81
- const KO_SHORT_REVIEW_SIGNAL_PATTERN_V4 = /리뷰|검토|점검/i;
82
- const KO_SHORT_PLAN_SIGNAL_PATTERN_V4 = /설계|로드맵|아키텍처|계획.{0,6}세워|기획/i;
83
- const KO_SHORT_REFACTOR_SIGNAL_PATTERN_V4 = /리팩토링|리팩터링|구조\s*개선/i;
84
- const KO_SHORT_CODE_GEN_SIGNAL_PATTERN_V4 = /구현|만들|생성|작성|추가|테스트|삭제|지워|지우|제거|고쳐|고치|수정|변경|바꿔|바꾸|옮겨|옮기|넣어/i;
85
296
  /** Short-range compound-clause conjunction boundary. Only bare/compound "then"/"and also"/";" — never bare "and". */
86
297
  const COMPOUND_SPLIT_PATTERN = /\band\s+then\b|\bthen\b|\band\s+also\b|;/i;
87
298
  /**
@@ -116,22 +327,22 @@ function firstClause(prompt) {
116
327
  // never match one of these: none of the alternations include "don't"/"never"/
117
328
  // etc, so they are negation-immune by construction.
118
329
  function hasLeadingReviewIntent(text) {
119
- return /^(?:review|audit(?!\s+log\b)|critique|inspect|assess|approve|double-?check|lgtm)\b/i.test(text);
330
+ return clusterMatchesRoleV4(text, "review", "leading-intent");
120
331
  }
121
332
  function hasLeadingPlanIntent(text) {
122
- return /^(?:plan|design|architect|decompose)\b|^(?:write|create)\s+(?:a\s+)?(?:technical\s+)?(?:spec|roadmap|strategy|plan)\b/i.test(text);
333
+ return clusterMatchesRoleV4(text, "plan", "leading-intent");
123
334
  }
124
335
  function hasLeadingRefactorIntent(text) {
125
- return /^(?:refactor(?:ing|ed)?|extract|rename|deduplicate|consolidate|modularize|reorganize|simplify|restructure|untangle)\b|^clean\s+up\b|^split\s+(?:the\s+)?module\b|^move\s+logic\b/i.test(text);
336
+ return clusterMatchesRoleV4(text, "refactor", "leading-intent");
126
337
  }
127
338
  function hasLeadingDebugAction(text) {
128
- return /^(?:debug|investigate\s+why|reproduce|trace\b)|^fix\s+this\s+(?:traceback|panic|error)\b/i.test(text);
339
+ return clusterMatchesRoleV4(text, "debug", "leading-intent");
129
340
  }
130
341
  function hasLeadingCodeGenIntent(text) {
131
- return /^(?:implement|write|create|build|generate|scaffold|prototype|add)\b/i.test(text);
342
+ return clusterMatchesRoleV4(text, "code-gen", "leading-intent");
132
343
  }
133
344
  function hasLeadingSimpleEditIntent(text) {
134
- return /^(?:correct|update|swap|remove|adjust|fix|add|change|trim|reword|tweak|bump)\b/i.test(text);
345
+ return clusterMatchesRoleV4(text, "simple-edit", "leading-intent");
135
346
  }
136
347
  const NO_MATCH_RESULT_V4 = { matched: false, suppressed: false };
137
348
  function matchOperationalRunbookV4(prompt, windowChars) {
@@ -199,9 +410,21 @@ function matchUnnegated(prompt, pattern, windowChars) {
199
410
  }
200
411
  return { matched: sawUnnegated, suppressed: sawMatch && !sawUnnegated };
201
412
  }
202
- // ============================================================================
203
- // Bounded compound-intent detection
204
- // ============================================================================
413
+ function matchClusterUnnegatedV4(prompt, taskClass, role, windowChars) {
414
+ let sawSuppressed = false;
415
+ for (const cluster of INTENT_LEXEME_CLUSTERS_V4) {
416
+ if (cluster.taskClass !== taskClass || cluster.role !== role)
417
+ continue;
418
+ for (const pattern of cluster.phrases) {
419
+ const result = matchUnnegated(prompt, pattern, windowChars);
420
+ if (result.matched)
421
+ return { matched: true, suppressed: false };
422
+ if (result.suppressed)
423
+ sawSuppressed = true;
424
+ }
425
+ }
426
+ return { matched: false, suppressed: sawSuppressed };
427
+ }
205
428
  /**
206
429
  * Splits off a bounded second clause after a short-range conjunction
207
430
  * ("then"/"and then"/"and also"/";"), only when the split point is within the
@@ -213,19 +436,33 @@ function splitCompoundClauseV4(prompt) {
213
436
  const match = COMPOUND_SPLIT_PATTERN.exec(prompt);
214
437
  if (match === null || match.index > COMPOUND_SPLIT_MAX_INDEX_V4)
215
438
  return null;
216
- const second = prompt.slice(match.index + match[0].length).trim();
439
+ const startIndex = match.index + match[0].length;
440
+ const second = prompt.slice(startIndex).trim();
217
441
  if (second.length < COMPOUND_SECOND_CLAUSE_MIN_CHARS_V4)
218
442
  return null;
219
- return second;
443
+ return { text: second, startIndex };
444
+ }
445
+ function hasFutureSelfClauseV4(clause) {
446
+ return /^(?:(?:i|we)(?:'|’)ll\b|(?:i|we)\s+will\b|(?:i|we)(?:'|’)m\s+going\s+to\b|before\s+(?:i|we)\b|after\s+(?:i|we)\b)/i.test(clause);
447
+ }
448
+ function agentDirectedSecondClauseV4(clause) {
449
+ if (hasFutureSelfClauseV4(clause))
450
+ return null;
451
+ return clause.replace(/^(?:please|pls|also|then|can\s+you|could\s+you|would\s+you|help\s+me(?:\s+to)?)\s+/i, "");
220
452
  }
221
453
  /**
222
454
  * Leading-intent test for a compound prompt's SECOND clause. Reuses the same
223
455
  * `^`-anchored leading-intent tests as the primary clause (so it is equally
224
456
  * negation-immune), without the primary clause's diagnosticEvidence/localEdit
225
457
  * gating (a short second-clause fragment carries no such context of its own).
458
+ * First-person future/context clauses ("I'll...", "we will...", "before I...")
459
+ * are user context, not an agent-directed second task.
226
460
  */
227
461
  function secondClauseLeadingIntentV4(clause) {
228
- const bounded = clause.slice(0, COMPOUND_SECOND_CLAUSE_SCAN_CHARS_V4);
462
+ const agentDirectedClause = agentDirectedSecondClauseV4(clause);
463
+ if (agentDirectedClause === null)
464
+ return null;
465
+ const bounded = agentDirectedClause.slice(0, COMPOUND_SECOND_CLAUSE_SCAN_CHARS_V4);
229
466
  return leadingIntentFromLeadingClauseV4(bounded);
230
467
  }
231
468
  function leadingIntentFromLeadingClauseV4(leading) {
@@ -248,18 +485,27 @@ function leadingIntentIsPostNegatedV4(leading, windowChars) {
248
485
  return (leadingIntentMatch !== null && hasPostPositionedNegationCueV4(leading, leadingIntentMatch[0].length, windowChars));
249
486
  }
250
487
  function classifyShortKoreanZeroScoreTaskV4(prompt, windowChars) {
251
- if (matchUnnegated(prompt, KO_SHORT_DEBUG_SIGNAL_PATTERN_V4, windowChars).matched)
488
+ const generalizedEvidence = extractGeneralizedIntentEvidenceV4(prompt);
489
+ for (const taskClass of TASK_CLASSES_V4) {
490
+ if (generalizedEvidence.skeletonMatch[taskClass])
491
+ return taskClass;
492
+ }
493
+ if (matchClusterUnnegatedV4(prompt, "debug", "korean-morphology", windowChars).matched)
252
494
  return "debug";
253
- if (matchUnnegated(prompt, KO_SHORT_SIMPLE_EDIT_SIGNAL_PATTERN_V4, windowChars).matched)
495
+ if (matchClusterUnnegatedV4(prompt, "simple-edit", "korean-morphology", windowChars).matched)
254
496
  return "simple-edit";
255
- if (matchUnnegated(prompt, KO_SHORT_REVIEW_SIGNAL_PATTERN_V4, windowChars).matched)
497
+ if (matchClusterUnnegatedV4(prompt, "review", "korean-morphology", windowChars).matched)
256
498
  return "review";
257
- if (matchUnnegated(prompt, KO_SHORT_PLAN_SIGNAL_PATTERN_V4, windowChars).matched)
499
+ if (matchClusterUnnegatedV4(prompt, "plan", "korean-morphology", windowChars).matched)
258
500
  return "plan";
259
- if (matchUnnegated(prompt, KO_SHORT_REFACTOR_SIGNAL_PATTERN_V4, windowChars).matched)
501
+ if (matchClusterUnnegatedV4(prompt, "refactor", "korean-morphology", windowChars).matched)
260
502
  return "refactor";
261
- if (matchUnnegated(prompt, KO_SHORT_CODE_GEN_SIGNAL_PATTERN_V4, windowChars).matched)
503
+ if (matchClusterUnnegatedV4(prompt, "code-gen", "korean-morphology", windowChars).matched)
262
504
  return "code-gen";
505
+ for (const taskClass of TASK_CLASSES_V4) {
506
+ if (generalizedEvidence.clusterMatch[taskClass])
507
+ return taskClass;
508
+ }
263
509
  return null;
264
510
  }
265
511
  /** Decision order for the leading intent classifier. */
@@ -288,6 +534,27 @@ function hasDiagnosticEvidenceV4(leading, hardDiagnosticMatched, bugObjectMatche
288
534
  return false;
289
535
  return !hasLeadingReviewIntent(leading) && !hasLeadingPlanIntent(leading) && !hasLeadingRefactorIntent(leading);
290
536
  }
537
+ function isDeferredClassV4(evidence, taskClass) {
538
+ return evidence.deferredClasses.includes(taskClass);
539
+ }
540
+ function suppressGeneralizedClassesV4(evidence, suppressedClasses) {
541
+ if (suppressedClasses.length === 0)
542
+ return evidence;
543
+ const clusterMatch = { ...evidence.clusterMatch };
544
+ const skeletonMatch = { ...evidence.skeletonMatch };
545
+ for (const taskClass of suppressedClasses) {
546
+ clusterMatch[taskClass] = false;
547
+ skeletonMatch[taskClass] = false;
548
+ }
549
+ return { ...evidence, clusterMatch, skeletonMatch };
550
+ }
551
+ function countGeneralizedEvidenceV4(evidence) {
552
+ const counts = emptyScoresV4();
553
+ for (const taskClass of TASK_CLASSES_V4) {
554
+ counts[taskClass] = (evidence.clusterMatch[taskClass] ? 1 : 0) + (evidence.skeletonMatch[taskClass] ? 1 : 0);
555
+ }
556
+ return counts;
557
+ }
291
558
  /**
292
559
  * Extracts every v4 feature from one prompt, negation-gating every
293
560
  * whole-prompt scan exactly once and recording a `negation:<channel>` id in
@@ -296,65 +563,106 @@ function hasDiagnosticEvidenceV4(leading, hardDiagnosticMatched, bugObjectMatche
296
563
  function extractFeaturesV4(prompt, weights, suppressed) {
297
564
  const leading = firstClause(prompt);
298
565
  const window = weights.negationWindowChars;
299
- const implementationObjectResult = matchUnnegated(prompt, IMPLEMENTATION_OBJECT_PATTERN, window);
566
+ const secondClause = splitCompoundClauseV4(prompt);
567
+ const agentSignalPrompt = secondClause !== null && hasFutureSelfClauseV4(secondClause.text)
568
+ ? prompt.slice(0, secondClause.startIndex).trim()
569
+ : prompt;
570
+ const generalizedEvidence = extractGeneralizedIntentEvidenceV4(agentSignalPrompt);
571
+ const shortKoreanPrompt = prompt.length < TRIVIAL_MAX_CHARS_V4 && /[가-힣]/.test(prompt);
572
+ const shortKoreanDebugFallbackOnly = shortKoreanPrompt &&
573
+ (generalizedEvidence.clusterMatch.debug || generalizedEvidence.skeletonMatch.debug) &&
574
+ !generalizedEvidence.clusterMatch.refactor &&
575
+ !generalizedEvidence.skeletonMatch.refactor &&
576
+ !generalizedEvidence.clusterMatch.review &&
577
+ !generalizedEvidence.skeletonMatch.review;
578
+ const scoredGeneralizedEvidence = suppressGeneralizedClassesV4(generalizedEvidence, shortKoreanDebugFallbackOnly
579
+ ? [...generalizedEvidence.deferredClasses, "debug"]
580
+ : generalizedEvidence.deferredClasses);
581
+ for (const taskClass of generalizedEvidence.deferredClasses)
582
+ suppressed.push(`deferral:${taskClass}`);
583
+ const implementationObjectResult = matchUnnegated(agentSignalPrompt, IMPLEMENTATION_OBJECT_PATTERN, window);
300
584
  if (implementationObjectResult.suppressed)
301
585
  suppressed.push("negation:implementation-object");
302
- const implementationObject = implementationObjectResult.matched;
586
+ const implementationObject = !shortKoreanDebugFallbackOnly && implementationObjectResult.matched;
587
+ const codeGenArtifactResult = matchClusterUnnegatedV4(agentSignalPrompt, "code-gen", "object-shape", window);
588
+ if (codeGenArtifactResult.suppressed)
589
+ suppressed.push("negation:code-gen-artifact-request");
590
+ const codeGenArtifactRequest = codeGenArtifactResult.matched;
303
591
  const lowRiskEditAction = LOW_RISK_EDIT_ACTION_PATTERN.test(leading);
304
- const localEditObjectResult = matchUnnegated(prompt, LOCAL_EDIT_OBJECT_PATTERN, window);
592
+ const localEditObjectResult = matchUnnegated(agentSignalPrompt, LOCAL_EDIT_OBJECT_PATTERN, window);
305
593
  if (localEditObjectResult.suppressed)
306
594
  suppressed.push("negation:local-edit-object");
307
- const localEditActionResult = matchUnnegated(prompt, LOCAL_EDIT_ACTION_OBJECT_PATTERN, window);
595
+ const localEditActionResult = matchUnnegated(agentSignalPrompt, LOCAL_EDIT_ACTION_OBJECT_PATTERN, window);
308
596
  if (localEditActionResult.suppressed)
309
597
  suppressed.push("negation:local-edit-action-object");
310
- const localEdit = !implementationObject && (lowRiskEditAction || localEditObjectResult.matched || localEditActionResult.matched);
311
- const hardDiagnosticResult = matchUnnegated(prompt, HARD_DIAGNOSTIC_PATTERN, window);
598
+ const localEdit = !implementationObject &&
599
+ !scoredGeneralizedEvidence.clusterMatch.review &&
600
+ !scoredGeneralizedEvidence.skeletonMatch.review &&
601
+ (lowRiskEditAction || localEditObjectResult.matched || localEditActionResult.matched);
602
+ const hardDiagnosticResult = matchUnnegated(agentSignalPrompt, HARD_DIAGNOSTIC_PATTERN, window);
312
603
  if (hardDiagnosticResult.suppressed)
313
604
  suppressed.push("negation:hard-diagnostic");
314
- const bugObjectResult = matchUnnegated(prompt, BUG_OBJECT_PATTERN, window);
605
+ const bugObjectResult = matchUnnegated(agentSignalPrompt, BUG_OBJECT_PATTERN, window);
315
606
  if (bugObjectResult.suppressed)
316
607
  suppressed.push("negation:bug-object");
317
- const genericDiagnosticResult = matchUnnegated(prompt, GENERIC_DIAGNOSTIC_PATTERN, window);
318
- const nonDiagnosticContext = NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN.test(prompt);
608
+ const genericDiagnosticResult = matchUnnegated(agentSignalPrompt, GENERIC_DIAGNOSTIC_PATTERN, window);
609
+ const nonDiagnosticContext = NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN.test(agentSignalPrompt);
319
610
  if (genericDiagnosticResult.suppressed && !nonDiagnosticContext)
320
611
  suppressed.push("negation:generic-diagnostic");
321
- const diagnosticEvidence = hasDiagnosticEvidenceV4(leading, hardDiagnosticResult.matched, bugObjectResult.matched, genericDiagnosticResult.matched, nonDiagnosticContext);
322
- const reviewScopeResult = matchUnnegated(prompt, REVIEW_SCOPE_PATTERN, window);
612
+ const diagnosticEvidence = !isDeferredClassV4(generalizedEvidence, "debug") &&
613
+ hasDiagnosticEvidenceV4(leading, hardDiagnosticResult.matched, bugObjectResult.matched, genericDiagnosticResult.matched, nonDiagnosticContext);
614
+ const reviewScopeResult = matchUnnegated(agentSignalPrompt, REVIEW_SCOPE_PATTERN, window);
323
615
  if (reviewScopeResult.suppressed)
324
616
  suppressed.push("negation:review-scope");
325
- const reviewScope = hasLeadingReviewIntent(leading) && reviewScopeResult.matched;
326
- const planBriefResult = matchUnnegated(prompt, PLAN_BRIEF_PATTERN, window);
617
+ const reviewScope = !isDeferredClassV4(generalizedEvidence, "review") && hasLeadingReviewIntent(leading) && reviewScopeResult.matched;
618
+ const planBriefResult = matchUnnegated(agentSignalPrompt, PLAN_BRIEF_PATTERN, window);
327
619
  if (planBriefResult.suppressed)
328
620
  suppressed.push("negation:plan-brief");
329
- const planBrief = hasLeadingPlanIntent(leading) && (planBriefResult.matched || prompt.length >= LONG_BRIEF_MIN_CHARS_V4);
330
- const operationalRunbookResult = matchOperationalRunbookV4(prompt, window);
621
+ const planBrief = !isDeferredClassV4(generalizedEvidence, "plan") &&
622
+ hasLeadingPlanIntent(leading) &&
623
+ (planBriefResult.matched || prompt.length >= LONG_BRIEF_MIN_CHARS_V4);
624
+ const operationalRunbookResult = matchOperationalRunbookV4(agentSignalPrompt, window);
331
625
  if (operationalRunbookResult.suppressed)
332
626
  suppressed.push("negation:operational-runbook");
333
627
  const operationalRunbook = operationalRunbookResult.matched;
334
- const refactorCueResult = matchUnnegated(prompt, REFACTOR_CUE_PATTERN, window);
628
+ const refactorCueResult = matchUnnegated(agentSignalPrompt, REFACTOR_CUE_PATTERN, window);
335
629
  if (refactorCueResult.suppressed)
336
630
  suppressed.push("negation:refactor-cue");
337
- const refactorCue = refactorCueResult.matched;
631
+ const refactorCue = !isDeferredClassV4(generalizedEvidence, "refactor") && refactorCueResult.matched;
338
632
  const rawPrimaryIntent = leadingIntentV4({ firstClause: leading, localEdit, diagnosticEvidence });
339
- const primaryIntent = rawPrimaryIntent !== null && leadingIntentIsPostNegatedV4(leading, window) ? null : rawPrimaryIntent;
340
- const secondClauseText = splitCompoundClauseV4(prompt);
341
- const secondClauseIntent = secondClauseText === null ? null : secondClauseLeadingIntentV4(secondClauseText);
633
+ const primaryIntent = rawPrimaryIntent !== null &&
634
+ (leadingIntentIsPostNegatedV4(leading, window) || isDeferredClassV4(generalizedEvidence, rawPrimaryIntent))
635
+ ? null
636
+ : rawPrimaryIntent;
637
+ const secondClauseIntent = secondClause === null ? null : secondClauseLeadingIntentV4(secondClause.text);
342
638
  const compoundIntent = secondClauseIntent !== null && secondClauseIntent !== primaryIntent;
343
639
  const debugKeywordResult = diagnosticEvidence
344
- ? matchUnnegated(prompt, DEBUG_KEYWORD_FAMILY_PATTERN, window)
640
+ ? matchClusterUnnegatedV4(agentSignalPrompt, "debug", "whole-prompt", window)
345
641
  : NO_MATCH_RESULT_V4;
346
642
  if (debugKeywordResult.suppressed)
347
643
  suppressed.push("negation:keyword-debug");
348
- const reviewKeywordResult = matchUnnegated(prompt, REVIEW_KEYWORD_FAMILY_PATTERN, window);
644
+ const reviewKeywordResult = isDeferredClassV4(generalizedEvidence, "review")
645
+ ? NO_MATCH_RESULT_V4
646
+ : matchClusterUnnegatedV4(agentSignalPrompt, "review", "whole-prompt", window);
349
647
  if (reviewKeywordResult.suppressed)
350
648
  suppressed.push("negation:keyword-review");
351
- const planKeywordResult = matchUnnegated(prompt, PLAN_KEYWORD_FAMILY_PATTERN, window);
649
+ const planKeywordResult = isDeferredClassV4(generalizedEvidence, "plan")
650
+ ? NO_MATCH_RESULT_V4
651
+ : matchClusterUnnegatedV4(agentSignalPrompt, "plan", "whole-prompt", window);
352
652
  if (planKeywordResult.suppressed)
353
653
  suppressed.push("negation:keyword-plan");
354
- const codeGenKeywordResult = matchUnnegated(prompt, CODE_GEN_KEYWORD_FAMILY_PATTERN, window);
654
+ const codeGenKeywordResult = matchClusterUnnegatedV4(agentSignalPrompt, "code-gen", "whole-prompt", window);
355
655
  if (codeGenKeywordResult.suppressed)
356
656
  suppressed.push("negation:keyword-code-gen");
357
- const addKeywordResult = localEdit ? NO_MATCH_RESULT_V4 : matchUnnegated(prompt, ADD_KEYWORD_PATTERN, window);
657
+ const evaluativeReviewResult = !isDeferredClassV4(generalizedEvidence, "review") && hasLeadingReviewIntent(leading)
658
+ ? matchClusterUnnegatedV4(agentSignalPrompt, "review", "object-shape", window)
659
+ : NO_MATCH_RESULT_V4;
660
+ if (evaluativeReviewResult.suppressed)
661
+ suppressed.push("negation:evaluative-review-object");
662
+ const evaluativeReviewObject = evaluativeReviewResult.matched;
663
+ const addKeywordResult = localEdit
664
+ ? NO_MATCH_RESULT_V4
665
+ : matchUnnegated(agentSignalPrompt, ADD_KEYWORD_PATTERN, window);
358
666
  if (addKeywordResult.suppressed)
359
667
  suppressed.push("negation:add-keyword");
360
668
  return {
@@ -368,6 +676,8 @@ function extractFeaturesV4(prompt, weights, suppressed) {
368
676
  operationalRunbook,
369
677
  refactorCue,
370
678
  implementationObject,
679
+ codeGenArtifactRequest,
680
+ evaluativeReviewObject,
371
681
  leadingIntent: primaryIntent,
372
682
  secondClauseIntent,
373
683
  compoundIntent,
@@ -380,6 +690,9 @@ function extractFeaturesV4(prompt, weights, suppressed) {
380
690
  "simple-edit": localEditObjectResult.matched,
381
691
  "code-gen": codeGenKeywordResult.matched,
382
692
  },
693
+ normalizedIntentClusterMatch: scoredGeneralizedEvidence.clusterMatch,
694
+ intentSkeletonMatch: scoredGeneralizedEvidence.skeletonMatch,
695
+ generalizedEvidenceCount: countGeneralizedEvidenceV4(scoredGeneralizedEvidence),
383
696
  addKeywordMatch: addKeywordResult.matched,
384
697
  };
385
698
  }
@@ -419,9 +732,17 @@ function computeScoresV4(features, weights) {
419
732
  scores.refactor += weights.refactorCue;
420
733
  if (features.implementationObject)
421
734
  scores["code-gen"] += weights.implementationObject;
735
+ if (features.codeGenArtifactRequest)
736
+ scores["code-gen"] += weights.codeGenArtifactRequest;
737
+ if (features.evaluativeReviewObject)
738
+ scores.review += weights.evaluativeReviewObject;
422
739
  for (const taskClass of TASK_CLASSES_V4) {
423
740
  if (features.keywordFamilyMatch[taskClass])
424
741
  scores[taskClass] += weights.keywordFamily[taskClass];
742
+ if (features.normalizedIntentClusterMatch[taskClass])
743
+ scores[taskClass] += weights.normalizedIntentCluster;
744
+ if (features.intentSkeletonMatch[taskClass])
745
+ scores[taskClass] += weights.intentSkeleton;
425
746
  }
426
747
  if (features.addKeywordMatch)
427
748
  scores["code-gen"] += weights.addKeyword;
@@ -449,6 +770,16 @@ function applyExtensionSignalsV4(scores, input, weights) {
449
770
  scores.plan += bump;
450
771
  }
451
772
  }
773
+ function clampConfidenceV4(value) {
774
+ return Math.max(0, Math.min(1, value));
775
+ }
776
+ function confidenceEvidenceStrengthV4(topScore, margin, weights) {
777
+ if (topScore <= 0)
778
+ return 0;
779
+ const separation = clampConfidenceV4(margin / topScore);
780
+ const evidenceFloor = weights.leadingIntent > 0 ? weights.leadingIntent : 1;
781
+ return Math.min(separation, clampConfidenceV4(topScore / evidenceFloor));
782
+ }
452
783
  // ============================================================================
453
784
  // Classifier
454
785
  // ============================================================================
@@ -458,7 +789,7 @@ function applyExtensionSignalsV4(scores, input, weights) {
458
789
  * Pipeline: extract negation/compound-aware contextual features -> score every
459
790
  * class from `weights` -> argmax with `TASK_CLASSES_V4` precedence tie-break,
460
791
  * tracking the runner-up -> compute margin/confidence/confidenceBand/tieBreak
461
- * from the RAW scores -> if `topScore <= 0`, replace `taskClass` (and record
792
+ * from RAW score separation plus evidence strength -> if `topScore <= 0`, replace `taskClass` (and record
462
793
  * `fallbackReason`) via the zero-score cascade (fence/diff -> code-gen;
463
794
  * length < 40 -> trivial; length >= 2400 -> plan; lane fallback; default
464
795
  * code-gen) — the raw scores/margin/runnerUp/tieBreak fields still reflect the
@@ -488,7 +819,12 @@ export function classifyTaskV4(input, weights = DEFAULT_WEIGHTS_V4) {
488
819
  const runnerUpScore = runnerUp !== null ? scores[runnerUp] : 0;
489
820
  const margin = topScore - runnerUpScore;
490
821
  const tieBreak = runnerUp !== null && topScore === runnerUpScore;
491
- const confidence = topScore <= 0 ? 0 : Math.max(0, Math.min(1, margin / topScore));
822
+ const rawConfidence = confidenceEvidenceStrengthV4(topScore, margin, weights);
823
+ const singleGeneralizedEvidence = features.generalizedEvidenceCount[top] === 1 &&
824
+ topScore <= Math.max(weights.normalizedIntentCluster, weights.intentSkeleton);
825
+ const confidence = singleGeneralizedEvidence
826
+ ? Math.min(rawConfidence, Math.max(0, weights.highConfidenceThreshold - 0.01))
827
+ : rawConfidence;
492
828
  const confidenceBand = confidence <= weights.lowConfidenceThreshold
493
829
  ? "low"
494
830
  : confidence >= weights.highConfidenceThreshold