open-multi-agent-kit 0.90.3 → 0.90.5

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 (184) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +16 -14
  3. package/dist/config.d.ts +3 -6
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +27 -19
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/adaptorch-bridge.d.ts +250 -0
  8. package/dist/core/adaptorch-bridge.d.ts.map +1 -0
  9. package/dist/core/adaptorch-bridge.js +466 -0
  10. package/dist/core/adaptorch-bridge.js.map +1 -0
  11. package/dist/core/agent-session.d.ts +97 -0
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +194 -0
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/bang-skill-invocation.d.ts.map +1 -1
  16. package/dist/core/bang-skill-invocation.js +149 -0
  17. package/dist/core/bang-skill-invocation.js.map +1 -1
  18. package/dist/core/compaction/utils.d.ts.map +1 -1
  19. package/dist/core/compaction/utils.js +15 -2
  20. package/dist/core/compaction/utils.js.map +1 -1
  21. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  22. package/dist/core/context-budget-v2-planner.js +10 -2
  23. package/dist/core/context-budget-v2-planner.js.map +1 -1
  24. package/dist/core/context-budget-v2-types.d.ts +7 -1
  25. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  26. package/dist/core/context-budget-v2-types.js.map +1 -1
  27. package/dist/core/domain-loadouts.d.ts.map +1 -1
  28. package/dist/core/domain-loadouts.js +1 -1
  29. package/dist/core/domain-loadouts.js.map +1 -1
  30. package/dist/core/reasoning-router-bias.d.ts +107 -0
  31. package/dist/core/reasoning-router-bias.d.ts.map +1 -0
  32. package/dist/core/reasoning-router-bias.js +278 -0
  33. package/dist/core/reasoning-router-bias.js.map +1 -0
  34. package/dist/core/reasoning-router-resolver.d.ts +44 -0
  35. package/dist/core/reasoning-router-resolver.d.ts.map +1 -0
  36. package/dist/core/reasoning-router-resolver.js +79 -0
  37. package/dist/core/reasoning-router-resolver.js.map +1 -0
  38. package/dist/core/reasoning-router-v4-weights.d.ts +80 -0
  39. package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -0
  40. package/dist/core/reasoning-router-v4-weights.js +68 -0
  41. package/dist/core/reasoning-router-v4-weights.js.map +1 -0
  42. package/dist/core/reasoning-router-v4.d.ts +118 -0
  43. package/dist/core/reasoning-router-v4.d.ts.map +1 -0
  44. package/dist/core/reasoning-router-v4.js +585 -0
  45. package/dist/core/reasoning-router-v4.js.map +1 -0
  46. package/dist/core/router-feedback-collector.d.ts +98 -0
  47. package/dist/core/router-feedback-collector.d.ts.map +1 -0
  48. package/dist/core/router-feedback-collector.js +236 -0
  49. package/dist/core/router-feedback-collector.js.map +1 -0
  50. package/dist/core/settings-manager.d.ts +28 -0
  51. package/dist/core/settings-manager.d.ts.map +1 -1
  52. package/dist/core/settings-manager.js +16 -0
  53. package/dist/core/settings-manager.js.map +1 -1
  54. package/dist/core/slash-commands.d.ts.map +1 -1
  55. package/dist/core/slash-commands.js +4 -1
  56. package/dist/core/slash-commands.js.map +1 -1
  57. package/dist/modes/interactive/components/control-panel-runtime-status.d.ts +15 -0
  58. package/dist/modes/interactive/components/control-panel-runtime-status.d.ts.map +1 -1
  59. package/dist/modes/interactive/components/control-panel-runtime-status.js +95 -5
  60. package/dist/modes/interactive/components/control-panel-runtime-status.js.map +1 -1
  61. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  62. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  63. package/dist/modes/interactive/interactive-mode.js +60 -17
  64. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  65. package/docs/loadout-domains/README.md +8 -6
  66. package/docs/loadout-domains/ai-agent-ops.md +3 -2
  67. package/docs/loadout-domains/backend-api.md +3 -2
  68. package/docs/loadout-domains/data-science.md +3 -2
  69. package/docs/loadout-domains/devops-infra.md +2 -1
  70. package/docs/loadout-domains/docs-writing.md +7 -2
  71. package/docs/loadout-domains/frontend-ui.md +42 -11
  72. package/docs/loadout-domains/korean-document.md +83 -0
  73. package/docs/loadout-domains/mobile.md +1 -1
  74. package/docs/loadout-domains/qa-testing.md +4 -2
  75. package/docs/loadout-domains/security-audit.md +2 -1
  76. package/docs/loadout-domains/visual-qa.md +94 -0
  77. package/docs/skills.md +4 -1
  78. package/docs/usage.md +36 -3
  79. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  80. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  81. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  82. package/examples/extensions/gondolin/package-lock.json +2 -2
  83. package/examples/extensions/gondolin/package.json +1 -1
  84. package/examples/extensions/sandbox/package-lock.json +2 -2
  85. package/examples/extensions/sandbox/package.json +1 -1
  86. package/examples/extensions/subagent/agent-capability-router.test.ts +123 -0
  87. package/examples/extensions/subagent/agent-capability-router.ts +274 -0
  88. package/examples/extensions/subagent/agents/planner.md +3 -0
  89. package/examples/extensions/subagent/agents/reviewer.md +3 -0
  90. package/examples/extensions/subagent/agents/scout.md +3 -0
  91. package/examples/extensions/subagent/agents/worker.md +2 -0
  92. package/examples/extensions/subagent/agents.ts +14 -0
  93. package/examples/extensions/subagent/capabilities.ts +368 -0
  94. package/examples/extensions/subagent/domain-profiles.ts +1089 -0
  95. package/examples/extensions/subagent/index.ts +71 -3
  96. package/examples/extensions/with-deps/package-lock.json +2 -2
  97. package/examples/extensions/with-deps/package.json +1 -1
  98. package/examples/sdk/12-full-control.ts +1 -1
  99. package/npm-shrinkwrap.json +12 -12
  100. package/package.json +4 -4
  101. package/dist/agents/jailbreak/index.d.ts +0 -7
  102. package/dist/agents/jailbreak/index.d.ts.map +0 -1
  103. package/dist/agents/jailbreak/index.js +0 -7
  104. package/dist/agents/jailbreak/index.js.map +0 -1
  105. package/dist/agents/jailbreak/subagent-f.d.ts +0 -44
  106. package/dist/agents/jailbreak/subagent-f.d.ts.map +0 -1
  107. package/dist/agents/jailbreak/subagent-f.js +0 -60
  108. package/dist/agents/jailbreak/subagent-f.js.map +0 -1
  109. package/dist/cli/jailbreak-args.d.ts +0 -26
  110. package/dist/cli/jailbreak-args.d.ts.map +0 -1
  111. package/dist/cli/jailbreak-args.js +0 -86
  112. package/dist/cli/jailbreak-args.js.map +0 -1
  113. package/dist/commands/jailbreak.d.ts +0 -49
  114. package/dist/commands/jailbreak.d.ts.map +0 -1
  115. package/dist/commands/jailbreak.js +0 -201
  116. package/dist/commands/jailbreak.js.map +0 -1
  117. package/dist/core/compactor.d.ts +0 -42
  118. package/dist/core/compactor.d.ts.map +0 -1
  119. package/dist/core/compactor.js +0 -128
  120. package/dist/core/compactor.js.map +0 -1
  121. package/dist/core/token-optimizer.d.ts +0 -58
  122. package/dist/core/token-optimizer.d.ts.map +0 -1
  123. package/dist/core/token-optimizer.js +0 -179
  124. package/dist/core/token-optimizer.js.map +0 -1
  125. package/dist/encoding/index.d.ts +0 -6
  126. package/dist/encoding/index.d.ts.map +0 -1
  127. package/dist/encoding/index.js +0 -6
  128. package/dist/encoding/index.js.map +0 -1
  129. package/dist/encoding/l4-encoder.d.ts +0 -96
  130. package/dist/encoding/l4-encoder.d.ts.map +0 -1
  131. package/dist/encoding/l4-encoder.js +0 -474
  132. package/dist/encoding/l4-encoder.js.map +0 -1
  133. package/dist/fuzzing/index.d.ts +0 -6
  134. package/dist/fuzzing/index.d.ts.map +0 -1
  135. package/dist/fuzzing/index.js +0 -6
  136. package/dist/fuzzing/index.js.map +0 -1
  137. package/dist/fuzzing/mutation-engine.d.ts +0 -93
  138. package/dist/fuzzing/mutation-engine.d.ts.map +0 -1
  139. package/dist/fuzzing/mutation-engine.js +0 -244
  140. package/dist/fuzzing/mutation-engine.js.map +0 -1
  141. package/dist/jailbreak-extension.d.ts +0 -48
  142. package/dist/jailbreak-extension.d.ts.map +0 -1
  143. package/dist/jailbreak-extension.js +0 -47
  144. package/dist/jailbreak-extension.js.map +0 -1
  145. package/dist/modules/index.d.ts +0 -8
  146. package/dist/modules/index.d.ts.map +0 -1
  147. package/dist/modules/index.js +0 -7
  148. package/dist/modules/index.js.map +0 -1
  149. package/dist/modules/lrl-bypass.d.ts +0 -40
  150. package/dist/modules/lrl-bypass.d.ts.map +0 -1
  151. package/dist/modules/lrl-bypass.js +0 -84
  152. package/dist/modules/lrl-bypass.js.map +0 -1
  153. package/dist/modules/lrl-deps.d.ts +0 -38
  154. package/dist/modules/lrl-deps.d.ts.map +0 -1
  155. package/dist/modules/lrl-deps.js +0 -128
  156. package/dist/modules/lrl-deps.js.map +0 -1
  157. package/dist/multiturn/easl-chain.d.ts +0 -113
  158. package/dist/multiturn/easl-chain.d.ts.map +0 -1
  159. package/dist/multiturn/easl-chain.js +0 -351
  160. package/dist/multiturn/easl-chain.js.map +0 -1
  161. package/dist/multiturn/index.d.ts +0 -6
  162. package/dist/multiturn/index.d.ts.map +0 -1
  163. package/dist/multiturn/index.js +0 -6
  164. package/dist/multiturn/index.js.map +0 -1
  165. package/dist/routing/fallback-router.d.ts +0 -123
  166. package/dist/routing/fallback-router.d.ts.map +0 -1
  167. package/dist/routing/fallback-router.js +0 -355
  168. package/dist/routing/fallback-router.js.map +0 -1
  169. package/dist/routing/index.d.ts +0 -6
  170. package/dist/routing/index.d.ts.map +0 -1
  171. package/dist/routing/index.js +0 -6
  172. package/dist/routing/index.js.map +0 -1
  173. package/dist/types/jailbreak.d.ts +0 -246
  174. package/dist/types/jailbreak.d.ts.map +0 -1
  175. package/dist/types/jailbreak.js +0 -8
  176. package/dist/types/jailbreak.js.map +0 -1
  177. package/dist/utils/jailbreak/common.d.ts +0 -63
  178. package/dist/utils/jailbreak/common.d.ts.map +0 -1
  179. package/dist/utils/jailbreak/common.js +0 -130
  180. package/dist/utils/jailbreak/common.js.map +0 -1
  181. package/dist/utils/jailbreak/index.d.ts +0 -7
  182. package/dist/utils/jailbreak/index.d.ts.map +0 -1
  183. package/dist/utils/jailbreak/index.js +0 -7
  184. package/dist/utils/jailbreak/index.js.map +0 -1
@@ -0,0 +1,585 @@
1
+ /**
2
+ * Reasoning-router v4 — the sole `/think auto` classifier/resolver.
3
+ *
4
+ * Deterministic by construction: same (input, weights) -> same
5
+ * ClassifierVerdictV4; same (verdict, availableLevels, laneType, bias, hint) ->
6
+ * same ThinkingLevel. No clock, randomness, I/O, model calls, network access,
7
+ * or state mutation anywhere in this file (ThinkingLevel is a type-only import
8
+ * and is erased).
9
+ *
10
+ * `classifyTaskV4` returns a confidence-bearing verdict: task class, per-class
11
+ * scores, runner-up, margin, confidence band, tie-break flag, fallback reason,
12
+ * bounded-negation audit ids, and compound-intent metadata. None of this carries
13
+ * prompt text — every field is a bounded enum, number, boolean, or a closed set
14
+ * of short diagnostic-id strings.
15
+ *
16
+ * `resolveThinkingLevelV4WithUncertainty` starts from the canonical task-class
17
+ * rule table, applies lane/bias/hint adjustments, and only adds non-negative
18
+ * confidence escalation. A prompt cannot lower its own effort by asking the
19
+ * router to "think less"; low confidence or fallback routing can only hold or
20
+ * raise the resolved level.
21
+ */
22
+ import { DEFAULT_WEIGHTS_V4, TASK_CLASSES_V4, } from "./reasoning-router-v4-weights.js";
23
+ export { DEFAULT_WEIGHTS_V4, TASK_CLASSES_V4 } from "./reasoning-router-v4-weights.js";
24
+ import { resolveThinkingLevelCore, TASK_CLASS_THINKING_LEVELS } from "./reasoning-router-resolver.js";
25
+ /** Prompts shorter than this (trimmed) fall back to trivial when no real signal scores > 0. */
26
+ const TRIVIAL_MAX_CHARS_V4 = 40;
27
+ /** Prompts at/above this length with no real signal fall back to plan (long prose brief). */
28
+ const COMPLEX_PROSE_MIN_CHARS_V4 = 2400;
29
+ /** Plan-brief prompts at/above this length count as a "long brief" even without PLAN_BRIEF_PATTERN. */
30
+ const LONG_BRIEF_MIN_CHARS_V4 = 512;
31
+ /** Compound-clause split only applies within this many leading characters (short direct commands, not long prose). */
32
+ const COMPOUND_SPLIT_MAX_INDEX_V4 = 300;
33
+ /** Minimum trimmed length of a candidate second clause to be worth checking for a leading intent. */
34
+ const COMPOUND_SECOND_CLAUSE_MIN_CHARS_V4 = 3;
35
+ /** Bounded look-ahead cap when checking a second clause's leading intent (defense in depth; independent of regex cost). */
36
+ const COMPOUND_SECOND_CLAUSE_SCAN_CHARS_V4 = 200;
37
+ /** Lane fallback class, used only when every scored class is <= 0 (zero-score cascade). */
38
+ const LANE_FALLBACK_CLASS_V4 = {
39
+ planner: "plan",
40
+ security: "review",
41
+ explorer: "review",
42
+ coder: "code-gen",
43
+ reviewer: "review",
44
+ tester: "code-gen",
45
+ };
46
+ // ============================================================================
47
+ // Whole-prompt / leading-clause patterns used by the v4 scorer.
48
+ // ============================================================================
49
+ 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
+ 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
+ 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;
53
+ 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
+ const NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN = /\berror\s+(?:handling|messages?|budgets?)\b|에러\s*처리|오류\s*처리/i;
55
+ const GENERIC_DIAGNOSTIC_PATTERN = /\b(errors?|broken|wrong\s+results|rolls?\s+back|rollback)\b/i;
56
+ const PLAN_BRIEF_PATTERN = /\b(context\s+and\s+constraints|starting\s+state|target\s+state|cross-cutting|deliver:|deliverables?|roadmap|migration\s+wave|top\s+(?:ten\s+)?risks|component\s+diagram|data\s+model|architecture|go\/no-go|phased\s+(?:delivery|rollout)|bounded\s+contexts?|strangler\s+fig|event-driven|quarter-by-quarter|milestone)\b/i;
57
+ const OPERATIONAL_RUNBOOK_SIGNAL_PATTERNS = [
58
+ { pattern: /\b(?:commit|commits|committed|committing)\b|커밋/i, critical: false },
59
+ { pattern: /\b(?:push|pushed|pushing)\b|푸시|푸쉬/i, critical: true },
60
+ {
61
+ pattern: /\b(?:release|releases|tag|version\s+bump|bump\s+(?:the\s+)?version)\b|릴리즈|태그|버전/i,
62
+ critical: true,
63
+ },
64
+ { pattern: /\bnpm\s+publish\b|\bpublish(?:ing|ed)?\b|퍼블리시|배포/i, critical: true },
65
+ { pattern: /\b(?:CHANGELOG\.md|changelog|release\s+notes?)\b|채널로그|체인지로그/i, critical: false },
66
+ { pattern: /\bREADME\.md\b|\breadme\b/i, critical: false },
67
+ { pattern: /\bci\s*\/\s*cd\b|\bgithub\s+actions?\b|\bworkflow\b|깃허브\s*액션|깃헙\s*액션/i, critical: false },
68
+ ];
69
+ 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;
71
+ 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
+ const ADD_KEYWORD_PATTERN = /\badd\b/i;
73
+ 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
+ /** Short-range compound-clause conjunction boundary. Only bare/compound "then"/"and also"/";" — never bare "and". */
86
+ const COMPOUND_SPLIT_PATTERN = /\band\s+then\b|\bthen\b|\band\s+also\b|;/i;
87
+ /**
88
+ * Bounded negation-cue vocabulary (spec 008 plan.md "bounded negation window").
89
+ * Deliberately excludes bare "not" (too broad; verified against the current
90
+ * GOLD_SET that every listed cue here is either absent or occurs AFTER, not
91
+ * before, a tracked keyword — see this lane's evidence file for the audit).
92
+ */
93
+ const NEGATION_CUE_PATTERN = /\b(?:don't|do\s+not|doesn't|does\s+not|didn't|did\s+not|won't|will\s+not|shouldn't|should\s+not|wouldn't|would\s+not|can't|cannot|isn't|is\s+not|aren't|are\s+not|never|avoid|skip|without|instead\s+of|rather\s+than|no\s+need\s+(?:to|for)|no\s+longer\s+need(?:\s+(?:to|for))?)\b/i;
94
+ const DIRECT_NOT_OBJECT_CUE_PATTERN_V4 = /\b(?:not|no)\s+(?:a\s+|an\s+|the\s+)?$/i;
95
+ const DOUBLE_NEGATION_RESCUE_PATTERN_V4 = /\b(?:don't|do\s+not|doesn't|does\s+not|didn't|did\s+not|won't|will\s+not|shouldn't|should\s+not|wouldn't|would\s+not|can't|cannot)\s+(?:skip|avoid)\s+(?:the\s+|a\s+|an\s+)?$/i;
96
+ const DELIBERATIVE_QUESTION_RESCUE_PATTERN_V4 = /\b(?:shouldn't|should\s+not|wouldn't|would\s+not|can't|cannot)\s+(?:we|i|you)\s*$/i;
97
+ const POSTPOSITIONED_NEGATION_CUE_PATTERN_V4 = /^\s*(?:-\s*free\b|(?:is|are|was|were)?\s*(?:not\s+(?:needed|required|desired)|unnecessary|not\s+necessary)|(?:[은는이가을를도만]\s*)?(?:하지\s*(?:마(?:라|세요|십시오)?|말(?:고|아|라)?|않(?:아|는|고|을|게)?)|말고|금지))/i;
98
+ /** A closed set of clause-boundary characters; a negation cue never reaches across one of these into a prior clause. */
99
+ const CLAUSE_BOUNDARY_CHARS = [".", "!", "?", ";", ","];
100
+ function hasCodeFence(text) {
101
+ return text.includes("```");
102
+ }
103
+ /** Explicit hunk headers or `diff --git` count alone; bare +/- only count together. */
104
+ function hasDiffMarkers(text) {
105
+ if (/^@@[^\n]*@@/m.test(text) || /^diff --git /m.test(text))
106
+ return true;
107
+ return /^\+(?!\+)/m.test(text) && /^-(?!-)/m.test(text);
108
+ }
109
+ /** First line (after stripping one polite prefix), capped at 180 chars. */
110
+ function firstClause(prompt) {
111
+ const firstLine = prompt.replace(/^(?:please|pls|can you|could you|would you|help me|i need you to)\s+/i, "").split("\n")[0] ?? "";
112
+ return firstLine.slice(0, 180);
113
+ }
114
+ // --- Leading-clause intent tests. All are `^`-anchored against the LEADING
115
+ // clause text only, so a negation cue at the start ("don't refactor...") can
116
+ // never match one of these: none of the alternations include "don't"/"never"/
117
+ // etc, so they are negation-immune by construction.
118
+ function hasLeadingReviewIntent(text) {
119
+ return /^(?:review|audit(?!\s+log\b)|critique|inspect|assess|approve|double-?check|lgtm)\b/i.test(text);
120
+ }
121
+ 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);
123
+ }
124
+ 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);
126
+ }
127
+ function hasLeadingDebugAction(text) {
128
+ return /^(?:debug|investigate\s+why|reproduce|trace\b)|^fix\s+this\s+(?:traceback|panic|error)\b/i.test(text);
129
+ }
130
+ function hasLeadingCodeGenIntent(text) {
131
+ return /^(?:implement|write|create|build|generate|scaffold|prototype|add)\b/i.test(text);
132
+ }
133
+ function hasLeadingSimpleEditIntent(text) {
134
+ return /^(?:correct|update|swap|remove|adjust|fix|add|change|trim|reword|tweak|bump)\b/i.test(text);
135
+ }
136
+ const NO_MATCH_RESULT_V4 = { matched: false, suppressed: false };
137
+ function matchOperationalRunbookV4(prompt, windowChars) {
138
+ let distinctSignals = 0;
139
+ let criticalSignals = 0;
140
+ let sawSuppressed = false;
141
+ for (const { pattern, critical } of OPERATIONAL_RUNBOOK_SIGNAL_PATTERNS) {
142
+ const result = matchUnnegated(prompt, pattern, windowChars);
143
+ if (result.matched) {
144
+ distinctSignals += 1;
145
+ if (critical)
146
+ criticalSignals += 1;
147
+ }
148
+ if (result.suppressed)
149
+ sawSuppressed = true;
150
+ }
151
+ const explicitRelease = matchUnnegated(prompt, EXPLICIT_RELEASE_RUNBOOK_PATTERN, windowChars).matched;
152
+ const matched = explicitRelease || distinctSignals >= 3 || (criticalSignals >= 2 && distinctSignals >= 2);
153
+ return { matched, suppressed: !matched && sawSuppressed };
154
+ }
155
+ /**
156
+ * Negation-aware whole-prompt pattern scan. Finds every occurrence of `pattern`
157
+ * in `prompt`; for each, looks back up to
158
+ * `windowChars` characters (never crossing a `.,;!?` boundary into a prior
159
+ * clause) for a `NEGATION_CUE_PATTERN` hit, and looks forward inside the same
160
+ * bounded window for Hangul-only post-positioned negation cues such as
161
+ * "하지 말고". `matched` is true iff at least one occurrence survives
162
+ * un-negated. Deterministic, single pass per pattern, no shared regex state (a
163
+ * fresh global-flag RegExp is constructed per call).
164
+ */
165
+ function hasPrePositionedNegationCueV4(scoped) {
166
+ if (!NEGATION_CUE_PATTERN.test(scoped) && !DIRECT_NOT_OBJECT_CUE_PATTERN_V4.test(scoped))
167
+ return false;
168
+ return !DOUBLE_NEGATION_RESCUE_PATTERN_V4.test(scoped) && !DELIBERATIVE_QUESTION_RESCUE_PATTERN_V4.test(scoped);
169
+ }
170
+ function hasPostPositionedNegationCueV4(prompt, matchEnd, windowChars) {
171
+ const scoped = prompt.slice(matchEnd, Math.min(prompt.length, matchEnd + windowChars));
172
+ return POSTPOSITIONED_NEGATION_CUE_PATTERN_V4.test(scoped);
173
+ }
174
+ function matchUnnegated(prompt, pattern, windowChars) {
175
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
176
+ const globalPattern = new RegExp(pattern.source, flags);
177
+ let sawMatch = false;
178
+ let sawUnnegated = false;
179
+ let match = globalPattern.exec(prompt);
180
+ while (match !== null) {
181
+ sawMatch = true;
182
+ const matchStart = match.index;
183
+ const windowStart = Math.max(0, matchStart - windowChars);
184
+ const windowText = prompt.slice(windowStart, matchStart);
185
+ let boundary = -1;
186
+ for (const boundaryChar of CLAUSE_BOUNDARY_CHARS) {
187
+ const idx = windowText.lastIndexOf(boundaryChar);
188
+ if (idx > boundary)
189
+ boundary = idx;
190
+ }
191
+ const scoped = boundary >= 0 ? windowText.slice(boundary + 1) : windowText;
192
+ const preNegated = hasPrePositionedNegationCueV4(scoped);
193
+ const postNegated = !preNegated && hasPostPositionedNegationCueV4(prompt, matchStart + match[0].length, windowChars);
194
+ if (!preNegated && !postNegated)
195
+ sawUnnegated = true;
196
+ if (match[0].length === 0)
197
+ globalPattern.lastIndex += 1;
198
+ match = globalPattern.exec(prompt);
199
+ }
200
+ return { matched: sawUnnegated, suppressed: sawMatch && !sawUnnegated };
201
+ }
202
+ // ============================================================================
203
+ // Bounded compound-intent detection
204
+ // ============================================================================
205
+ /**
206
+ * Splits off a bounded second clause after a short-range conjunction
207
+ * ("then"/"and then"/"and also"/";"), only when the split point is within the
208
+ * first `COMPOUND_SPLIT_MAX_INDEX_V4` characters (short direct commands, not
209
+ * long prose briefs — verified this never fires inside the GOLD_SET's
210
+ * >=2400-char plan entries). Returns null when no qualifying split exists.
211
+ */
212
+ function splitCompoundClauseV4(prompt) {
213
+ const match = COMPOUND_SPLIT_PATTERN.exec(prompt);
214
+ if (match === null || match.index > COMPOUND_SPLIT_MAX_INDEX_V4)
215
+ return null;
216
+ const second = prompt.slice(match.index + match[0].length).trim();
217
+ if (second.length < COMPOUND_SECOND_CLAUSE_MIN_CHARS_V4)
218
+ return null;
219
+ return second;
220
+ }
221
+ /**
222
+ * Leading-intent test for a compound prompt's SECOND clause. Reuses the same
223
+ * `^`-anchored leading-intent tests as the primary clause (so it is equally
224
+ * negation-immune), without the primary clause's diagnosticEvidence/localEdit
225
+ * gating (a short second-clause fragment carries no such context of its own).
226
+ */
227
+ function secondClauseLeadingIntentV4(clause) {
228
+ const bounded = clause.slice(0, COMPOUND_SECOND_CLAUSE_SCAN_CHARS_V4);
229
+ return leadingIntentFromLeadingClauseV4(bounded);
230
+ }
231
+ function leadingIntentFromLeadingClauseV4(leading) {
232
+ if (hasLeadingDebugAction(leading))
233
+ return "debug";
234
+ if (hasLeadingReviewIntent(leading))
235
+ return "review";
236
+ if (hasLeadingPlanIntent(leading))
237
+ return "plan";
238
+ if (hasLeadingRefactorIntent(leading))
239
+ return "refactor";
240
+ if (hasLeadingSimpleEditIntent(leading))
241
+ return "simple-edit";
242
+ if (hasLeadingCodeGenIntent(leading))
243
+ return "code-gen";
244
+ return null;
245
+ }
246
+ function leadingIntentIsPostNegatedV4(leading, windowChars) {
247
+ const leadingIntentMatch = /^(?:debug|investigate\s+why|reproduce|trace\b|fix\s+this\s+(?:traceback|panic|error)|review|audit(?!\s+log\b)|critique|inspect|assess|approve|double-?check|lgtm|plan|design|architect|decompose|write\s+(?:a\s+)?(?:technical\s+)?(?:spec|roadmap|strategy|plan)|create\s+(?:a\s+)?(?:technical\s+)?(?:spec|roadmap|strategy|plan)|refactor(?:ing|ed)?|extract|rename|deduplicate|consolidate|modularize|reorganize|simplify|restructure|untangle|clean\s+up|split\s+(?:the\s+)?module|move\s+logic|correct|update|swap|remove|adjust|fix|add|change|trim|reword|tweak|bump|implement|build|generate|scaffold|prototype)\b/i.exec(leading);
248
+ return (leadingIntentMatch !== null && hasPostPositionedNegationCueV4(leading, leadingIntentMatch[0].length, windowChars));
249
+ }
250
+ function classifyShortKoreanZeroScoreTaskV4(prompt, windowChars) {
251
+ if (matchUnnegated(prompt, KO_SHORT_DEBUG_SIGNAL_PATTERN_V4, windowChars).matched)
252
+ return "debug";
253
+ if (matchUnnegated(prompt, KO_SHORT_SIMPLE_EDIT_SIGNAL_PATTERN_V4, windowChars).matched)
254
+ return "simple-edit";
255
+ if (matchUnnegated(prompt, KO_SHORT_REVIEW_SIGNAL_PATTERN_V4, windowChars).matched)
256
+ return "review";
257
+ if (matchUnnegated(prompt, KO_SHORT_PLAN_SIGNAL_PATTERN_V4, windowChars).matched)
258
+ return "plan";
259
+ if (matchUnnegated(prompt, KO_SHORT_REFACTOR_SIGNAL_PATTERN_V4, windowChars).matched)
260
+ return "refactor";
261
+ if (matchUnnegated(prompt, KO_SHORT_CODE_GEN_SIGNAL_PATTERN_V4, windowChars).matched)
262
+ return "code-gen";
263
+ return null;
264
+ }
265
+ /** Decision order for the leading intent classifier. */
266
+ function leadingIntentV4(input) {
267
+ if (input.diagnosticEvidence &&
268
+ (hasLeadingDebugAction(input.firstClause) || hasLeadingSimpleEditIntent(input.firstClause)))
269
+ return "debug";
270
+ if (hasLeadingReviewIntent(input.firstClause))
271
+ return "review";
272
+ if (hasLeadingPlanIntent(input.firstClause))
273
+ return "plan";
274
+ if (hasLeadingRefactorIntent(input.firstClause))
275
+ return "refactor";
276
+ if (input.localEdit && hasLeadingSimpleEditIntent(input.firstClause))
277
+ return "simple-edit";
278
+ if (!input.localEdit && hasLeadingCodeGenIntent(input.firstClause))
279
+ return "code-gen";
280
+ return null;
281
+ }
282
+ /** Diagnostic-evidence decision structure, parameterized over precomputed negation-aware matches. */
283
+ function hasDiagnosticEvidenceV4(leading, hardDiagnosticMatched, bugObjectMatched, genericDiagnosticMatched, nonDiagnosticContext) {
284
+ if (hasLeadingDebugAction(leading) || hardDiagnosticMatched)
285
+ return true;
286
+ const hasContextualDiagnostic = bugObjectMatched || (genericDiagnosticMatched && !nonDiagnosticContext);
287
+ if (!hasContextualDiagnostic)
288
+ return false;
289
+ return !hasLeadingReviewIntent(leading) && !hasLeadingPlanIntent(leading) && !hasLeadingRefactorIntent(leading);
290
+ }
291
+ /**
292
+ * Extracts every v4 feature from one prompt, negation-gating every
293
+ * whole-prompt scan exactly once and recording a `negation:<channel>` id in
294
+ * `suppressed` whenever a match existed but every occurrence was negated.
295
+ */
296
+ function extractFeaturesV4(prompt, weights, suppressed) {
297
+ const leading = firstClause(prompt);
298
+ const window = weights.negationWindowChars;
299
+ const implementationObjectResult = matchUnnegated(prompt, IMPLEMENTATION_OBJECT_PATTERN, window);
300
+ if (implementationObjectResult.suppressed)
301
+ suppressed.push("negation:implementation-object");
302
+ const implementationObject = implementationObjectResult.matched;
303
+ const lowRiskEditAction = LOW_RISK_EDIT_ACTION_PATTERN.test(leading);
304
+ const localEditObjectResult = matchUnnegated(prompt, LOCAL_EDIT_OBJECT_PATTERN, window);
305
+ if (localEditObjectResult.suppressed)
306
+ suppressed.push("negation:local-edit-object");
307
+ const localEditActionResult = matchUnnegated(prompt, LOCAL_EDIT_ACTION_OBJECT_PATTERN, window);
308
+ if (localEditActionResult.suppressed)
309
+ 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);
312
+ if (hardDiagnosticResult.suppressed)
313
+ suppressed.push("negation:hard-diagnostic");
314
+ const bugObjectResult = matchUnnegated(prompt, BUG_OBJECT_PATTERN, window);
315
+ if (bugObjectResult.suppressed)
316
+ suppressed.push("negation:bug-object");
317
+ const genericDiagnosticResult = matchUnnegated(prompt, GENERIC_DIAGNOSTIC_PATTERN, window);
318
+ const nonDiagnosticContext = NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN.test(prompt);
319
+ if (genericDiagnosticResult.suppressed && !nonDiagnosticContext)
320
+ 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);
323
+ if (reviewScopeResult.suppressed)
324
+ suppressed.push("negation:review-scope");
325
+ const reviewScope = hasLeadingReviewIntent(leading) && reviewScopeResult.matched;
326
+ const planBriefResult = matchUnnegated(prompt, PLAN_BRIEF_PATTERN, window);
327
+ if (planBriefResult.suppressed)
328
+ 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);
331
+ if (operationalRunbookResult.suppressed)
332
+ suppressed.push("negation:operational-runbook");
333
+ const operationalRunbook = operationalRunbookResult.matched;
334
+ const refactorCueResult = matchUnnegated(prompt, REFACTOR_CUE_PATTERN, window);
335
+ if (refactorCueResult.suppressed)
336
+ suppressed.push("negation:refactor-cue");
337
+ const refactorCue = refactorCueResult.matched;
338
+ 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);
342
+ const compoundIntent = secondClauseIntent !== null && secondClauseIntent !== primaryIntent;
343
+ const debugKeywordResult = diagnosticEvidence
344
+ ? matchUnnegated(prompt, DEBUG_KEYWORD_FAMILY_PATTERN, window)
345
+ : NO_MATCH_RESULT_V4;
346
+ if (debugKeywordResult.suppressed)
347
+ suppressed.push("negation:keyword-debug");
348
+ const reviewKeywordResult = matchUnnegated(prompt, REVIEW_KEYWORD_FAMILY_PATTERN, window);
349
+ if (reviewKeywordResult.suppressed)
350
+ suppressed.push("negation:keyword-review");
351
+ const planKeywordResult = matchUnnegated(prompt, PLAN_KEYWORD_FAMILY_PATTERN, window);
352
+ if (planKeywordResult.suppressed)
353
+ suppressed.push("negation:keyword-plan");
354
+ const codeGenKeywordResult = matchUnnegated(prompt, CODE_GEN_KEYWORD_FAMILY_PATTERN, window);
355
+ if (codeGenKeywordResult.suppressed)
356
+ suppressed.push("negation:keyword-code-gen");
357
+ const addKeywordResult = localEdit ? NO_MATCH_RESULT_V4 : matchUnnegated(prompt, ADD_KEYWORD_PATTERN, window);
358
+ if (addKeywordResult.suppressed)
359
+ suppressed.push("negation:add-keyword");
360
+ return {
361
+ firstClause: leading,
362
+ codeFence: hasCodeFence(prompt),
363
+ diffHunk: hasDiffMarkers(prompt),
364
+ localEdit,
365
+ diagnosticEvidence,
366
+ reviewScope,
367
+ planBrief,
368
+ operationalRunbook,
369
+ refactorCue,
370
+ implementationObject,
371
+ leadingIntent: primaryIntent,
372
+ secondClauseIntent,
373
+ compoundIntent,
374
+ keywordFamilyMatch: {
375
+ trivial: false,
376
+ debug: debugKeywordResult.matched,
377
+ refactor: refactorCue,
378
+ review: reviewKeywordResult.matched,
379
+ plan: planKeywordResult.matched,
380
+ "simple-edit": localEditObjectResult.matched,
381
+ "code-gen": codeGenKeywordResult.matched,
382
+ },
383
+ addKeywordMatch: addKeywordResult.matched,
384
+ };
385
+ }
386
+ // ============================================================================
387
+ // Scoring
388
+ // ============================================================================
389
+ function emptyScoresV4() {
390
+ return {
391
+ trivial: 0,
392
+ "simple-edit": 0,
393
+ "code-gen": 0,
394
+ debug: 0,
395
+ refactor: 0,
396
+ review: 0,
397
+ plan: 0,
398
+ };
399
+ }
400
+ function computeScoresV4(features, weights) {
401
+ const scores = emptyScoresV4();
402
+ if (features.codeFence || features.diffHunk)
403
+ scores["code-gen"] += weights.codeFenceOrDiff;
404
+ if (features.leadingIntent !== null)
405
+ scores[features.leadingIntent] += weights.leadingIntent;
406
+ if (features.secondClauseIntent !== null)
407
+ scores[features.secondClauseIntent] += weights.secondClauseIntent;
408
+ if (features.localEdit)
409
+ scores["simple-edit"] += weights.localEdit;
410
+ if (features.diagnosticEvidence)
411
+ scores.debug += weights.diagnosticEvidence;
412
+ if (features.reviewScope)
413
+ scores.review += weights.reviewScope;
414
+ if (features.planBrief)
415
+ scores.plan += weights.planBrief;
416
+ if (features.operationalRunbook)
417
+ scores.plan += weights.operationalRunbook;
418
+ if (features.refactorCue)
419
+ scores.refactor += weights.refactorCue;
420
+ if (features.implementationObject)
421
+ scores["code-gen"] += weights.implementationObject;
422
+ for (const taskClass of TASK_CLASSES_V4) {
423
+ if (features.keywordFamilyMatch[taskClass])
424
+ scores[taskClass] += weights.keywordFamily[taskClass];
425
+ }
426
+ if (features.addKeywordMatch)
427
+ scores["code-gen"] += weights.addKeyword;
428
+ return scores;
429
+ }
430
+ /**
431
+ * Extension signals (history / context-pressure / judge vote). Every
432
+ * coefficient is 0 under DEFAULT_WEIGHTS_V4 (inert until calibrated by a
433
+ * future governance-backed lane); the mechanism exists so
434
+ * `TaskClassifierInputV4`'s optional fields are meaningfully wired rather than
435
+ * silently accepted-and-ignored.
436
+ */
437
+ function applyExtensionSignalsV4(scores, input, weights) {
438
+ const priorClass = input.history !== undefined && input.history.length > 0 ? input.history[0] : null;
439
+ if (priorClass !== null && weights.multiTurnPrior !== 0)
440
+ scores[priorClass] += weights.multiTurnPrior;
441
+ const judgeVote = input.judgeVote ?? null;
442
+ if (judgeVote !== null && weights.judgeVote !== 0)
443
+ scores[judgeVote] += weights.judgeVote;
444
+ const pressureBucket = input.pressureBucket ?? 0;
445
+ if (pressureBucket > 0 && weights.pressureBucket !== 0) {
446
+ const bump = pressureBucket * weights.pressureBucket;
447
+ scores.debug += bump;
448
+ scores.review += bump;
449
+ scores.plan += bump;
450
+ }
451
+ }
452
+ // ============================================================================
453
+ // Classifier
454
+ // ============================================================================
455
+ /**
456
+ * Deterministic confidence-bearing classifier (spec 008 Req 2).
457
+ *
458
+ * Pipeline: extract negation/compound-aware contextual features -> score every
459
+ * class from `weights` -> argmax with `TASK_CLASSES_V4` precedence tie-break,
460
+ * tracking the runner-up -> compute margin/confidence/confidenceBand/tieBreak
461
+ * from the RAW scores -> if `topScore <= 0`, replace `taskClass` (and record
462
+ * `fallbackReason`) via the zero-score cascade (fence/diff -> code-gen;
463
+ * length < 40 -> trivial; length >= 2400 -> plan; lane fallback; default
464
+ * code-gen) — the raw scores/margin/runnerUp/tieBreak fields still reflect the
465
+ * pre-cascade computation, for audit purposes.
466
+ *
467
+ * Pure: same (input, weights) -> same ClassifierVerdictV4.
468
+ */
469
+ export function classifyTaskV4(input, weights = DEFAULT_WEIGHTS_V4) {
470
+ const prompt = input.prompt.trim();
471
+ const suppressed = [];
472
+ const features = extractFeaturesV4(prompt, weights, suppressed);
473
+ const scores = computeScoresV4(features, weights);
474
+ applyExtensionSignalsV4(scores, input, weights);
475
+ let top = TASK_CLASSES_V4[0];
476
+ for (const c of TASK_CLASSES_V4) {
477
+ if (scores[c] > scores[top])
478
+ top = c;
479
+ }
480
+ let runnerUp = null;
481
+ for (const c of TASK_CLASSES_V4) {
482
+ if (c === top)
483
+ continue;
484
+ if (runnerUp === null || scores[c] > scores[runnerUp])
485
+ runnerUp = c;
486
+ }
487
+ const topScore = scores[top];
488
+ const runnerUpScore = runnerUp !== null ? scores[runnerUp] : 0;
489
+ const margin = topScore - runnerUpScore;
490
+ const tieBreak = runnerUp !== null && topScore === runnerUpScore;
491
+ const confidence = topScore <= 0 ? 0 : Math.max(0, Math.min(1, margin / topScore));
492
+ const confidenceBand = confidence <= weights.lowConfidenceThreshold
493
+ ? "low"
494
+ : confidence >= weights.highConfidenceThreshold
495
+ ? "high"
496
+ : "medium";
497
+ let taskClass = top;
498
+ let fallbackReason = null;
499
+ if (topScore <= 0) {
500
+ if (features.codeFence || features.diffHunk) {
501
+ taskClass = "code-gen";
502
+ fallbackReason = "code-fence-or-diff";
503
+ }
504
+ else if (prompt.length < TRIVIAL_MAX_CHARS_V4) {
505
+ const shortKoreanTaskClass = classifyShortKoreanZeroScoreTaskV4(prompt, weights.negationWindowChars);
506
+ if (shortKoreanTaskClass !== null) {
507
+ taskClass = shortKoreanTaskClass;
508
+ fallbackReason = "ko-short-task-signal";
509
+ }
510
+ else {
511
+ taskClass = "trivial";
512
+ fallbackReason = "trivial-length";
513
+ }
514
+ }
515
+ else if (prompt.length >= COMPLEX_PROSE_MIN_CHARS_V4) {
516
+ taskClass = "plan";
517
+ fallbackReason = "long-prose";
518
+ }
519
+ else if (input.laneType !== undefined) {
520
+ taskClass = LANE_FALLBACK_CLASS_V4[input.laneType];
521
+ fallbackReason = "lane-fallback";
522
+ }
523
+ else {
524
+ taskClass = "code-gen";
525
+ fallbackReason = "default";
526
+ }
527
+ }
528
+ return {
529
+ taskClass,
530
+ scores,
531
+ runnerUp,
532
+ margin,
533
+ confidence,
534
+ confidenceBand,
535
+ tieBreak,
536
+ fallbackReason,
537
+ suppressedFeatureIds: suppressed,
538
+ compoundIntent: features.compoundIntent,
539
+ secondClauseIntent: features.secondClauseIntent,
540
+ };
541
+ }
542
+ // ============================================================================
543
+ // Resolver
544
+ // ============================================================================
545
+ /**
546
+ * Static rule table: task class -> recommended ThinkingLevel. Re-exported
547
+ * from the shared resolver core (byte-identical values; previously a
548
+ * duplicated literal here — see reasoning-router-resolver.ts).
549
+ */
550
+ export const TASK_CLASS_THINKING_LEVELS_V4 = TASK_CLASS_THINKING_LEVELS;
551
+ /**
552
+ * Bounded, strictly non-negative escalation applied when the verdict's own
553
+ * confidence is low or a fallback (not a real signal) decided the class. This
554
+ * is the ONLY place confidence touches the target index, and it can only add:
555
+ * confidence can never lower effort (spec 008 Req 2 acceptance criterion).
556
+ */
557
+ const LOW_CONFIDENCE_ESCALATION_STEPS_V4 = 1;
558
+ /**
559
+ * Thin auto-mode wrapper with no uncertainty adjustment: base rule table ->
560
+ * lane step -> clamp to availableLevels. This is v4's "confident" path and
561
+ * delegates to the shared resolver core with bias=0, hint=null, escalationSteps=0.
562
+ */
563
+ export function resolveThinkingLevelV4ForAuto(taskClass, availableLevels, laneType) {
564
+ return resolveThinkingLevelCore(taskClass, availableLevels, laneType, 0, null, 0);
565
+ }
566
+ /**
567
+ * Uncertainty-aware resolver (spec 008 Req 2 / plan.md step 5).
568
+ *
569
+ * Pipeline: base rule table for `verdict.taskClass` -> lane step -> bounded
570
+ * bias [-2,+2] -> optional hint fusion (±2 bounded) -> bounded
571
+ * non-negative confidence escalation (+1 ladder step exactly when
572
+ * `verdict.confidenceBand === "low"` or `verdict.fallbackReason !== null`) ->
573
+ * clamp to `availableLevels`.
574
+ *
575
+ * The confidence-escalation term is strictly `>= 0`: low confidence can only
576
+ * hold the base+lane+bias+hint target or push it one step higher, never lower
577
+ * it. This guarantees a low-confidence verdict never resolves BELOW what
578
+ * `resolveThinkingLevelV4ForAuto` would give the same class (with bias=0,
579
+ * hint=null) — text alone cannot talk the resolver down.
580
+ */
581
+ export function resolveThinkingLevelV4WithUncertainty(verdict, availableLevels, laneType, bias = 0, hint = null) {
582
+ const escalationSteps = verdict.confidenceBand === "low" || verdict.fallbackReason !== null ? LOW_CONFIDENCE_ESCALATION_STEPS_V4 : 0;
583
+ return resolveThinkingLevelCore(verdict.taskClass, availableLevels, laneType, bias, hint, escalationSteps);
584
+ }
585
+ //# sourceMappingURL=reasoning-router-v4.js.map