open-multi-agent-kit 0.90.3 → 0.90.4

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 (171) hide show
  1. package/CHANGELOG.md +16 -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 +257 -0
  8. package/dist/core/adaptorch-bridge.d.ts.map +1 -0
  9. package/dist/core/adaptorch-bridge.js +473 -0
  10. package/dist/core/adaptorch-bridge.js.map +1 -0
  11. package/dist/core/agent-session.d.ts +130 -0
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +286 -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/domain-loadouts.d.ts.map +1 -1
  22. package/dist/core/domain-loadouts.js +1 -1
  23. package/dist/core/domain-loadouts.js.map +1 -1
  24. package/dist/core/reasoning-router-bias.d.ts +119 -0
  25. package/dist/core/reasoning-router-bias.d.ts.map +1 -0
  26. package/dist/core/reasoning-router-bias.js +287 -0
  27. package/dist/core/reasoning-router-bias.js.map +1 -0
  28. package/dist/core/reasoning-router-v2.d.ts +94 -0
  29. package/dist/core/reasoning-router-v2.d.ts.map +1 -0
  30. package/dist/core/reasoning-router-v2.js +288 -0
  31. package/dist/core/reasoning-router-v2.js.map +1 -0
  32. package/dist/core/reasoning-router-v3.d.ts +15 -0
  33. package/dist/core/reasoning-router-v3.d.ts.map +1 -0
  34. package/dist/core/reasoning-router-v3.js +193 -0
  35. package/dist/core/reasoning-router-v3.js.map +1 -0
  36. package/dist/core/reasoning-router-v4-weights.d.ts +124 -0
  37. package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -0
  38. package/dist/core/reasoning-router-v4-weights.js +112 -0
  39. package/dist/core/reasoning-router-v4-weights.js.map +1 -0
  40. package/dist/core/reasoning-router-v4.d.ts +163 -0
  41. package/dist/core/reasoning-router-v4.d.ts.map +1 -0
  42. package/dist/core/reasoning-router-v4.js +683 -0
  43. package/dist/core/reasoning-router-v4.js.map +1 -0
  44. package/dist/core/reasoning-router-weights.d.ts +155 -0
  45. package/dist/core/reasoning-router-weights.d.ts.map +1 -0
  46. package/dist/core/reasoning-router-weights.js +204 -0
  47. package/dist/core/reasoning-router-weights.js.map +1 -0
  48. package/dist/core/reasoning-router.d.ts +44 -0
  49. package/dist/core/reasoning-router.d.ts.map +1 -0
  50. package/dist/core/reasoning-router.js +138 -0
  51. package/dist/core/reasoning-router.js.map +1 -0
  52. package/dist/core/router-feedback-collector.d.ts +101 -0
  53. package/dist/core/router-feedback-collector.d.ts.map +1 -0
  54. package/dist/core/router-feedback-collector.js +239 -0
  55. package/dist/core/router-feedback-collector.js.map +1 -0
  56. package/dist/core/settings-manager.d.ts +28 -0
  57. package/dist/core/settings-manager.d.ts.map +1 -1
  58. package/dist/core/settings-manager.js +16 -0
  59. package/dist/core/settings-manager.js.map +1 -1
  60. package/dist/core/slash-commands.d.ts.map +1 -1
  61. package/dist/core/slash-commands.js +4 -1
  62. package/dist/core/slash-commands.js.map +1 -1
  63. package/dist/modes/interactive/components/control-panel-runtime-status.d.ts +15 -0
  64. package/dist/modes/interactive/components/control-panel-runtime-status.d.ts.map +1 -1
  65. package/dist/modes/interactive/components/control-panel-runtime-status.js +95 -5
  66. package/dist/modes/interactive/components/control-panel-runtime-status.js.map +1 -1
  67. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  68. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  69. package/dist/modes/interactive/interactive-mode.js +143 -18
  70. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  71. package/docs/loadout-domains/README.md +8 -6
  72. package/docs/loadout-domains/ai-agent-ops.md +3 -2
  73. package/docs/loadout-domains/backend-api.md +3 -2
  74. package/docs/loadout-domains/data-science.md +3 -2
  75. package/docs/loadout-domains/devops-infra.md +2 -1
  76. package/docs/loadout-domains/docs-writing.md +7 -2
  77. package/docs/loadout-domains/frontend-ui.md +42 -11
  78. package/docs/loadout-domains/korean-document.md +83 -0
  79. package/docs/loadout-domains/mobile.md +1 -1
  80. package/docs/loadout-domains/qa-testing.md +4 -2
  81. package/docs/loadout-domains/security-audit.md +2 -1
  82. package/docs/loadout-domains/visual-qa.md +94 -0
  83. package/docs/skills.md +4 -1
  84. package/docs/usage.md +37 -3
  85. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  86. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  87. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  88. package/examples/extensions/gondolin/package-lock.json +2 -2
  89. package/examples/extensions/gondolin/package.json +1 -1
  90. package/examples/extensions/sandbox/package-lock.json +2 -2
  91. package/examples/extensions/sandbox/package.json +1 -1
  92. package/examples/extensions/with-deps/package-lock.json +2 -2
  93. package/examples/extensions/with-deps/package.json +1 -1
  94. package/npm-shrinkwrap.json +12 -12
  95. package/package.json +4 -4
  96. package/dist/agents/jailbreak/index.d.ts +0 -7
  97. package/dist/agents/jailbreak/index.d.ts.map +0 -1
  98. package/dist/agents/jailbreak/index.js +0 -7
  99. package/dist/agents/jailbreak/index.js.map +0 -1
  100. package/dist/agents/jailbreak/subagent-f.d.ts +0 -44
  101. package/dist/agents/jailbreak/subagent-f.d.ts.map +0 -1
  102. package/dist/agents/jailbreak/subagent-f.js +0 -60
  103. package/dist/agents/jailbreak/subagent-f.js.map +0 -1
  104. package/dist/cli/jailbreak-args.d.ts +0 -26
  105. package/dist/cli/jailbreak-args.d.ts.map +0 -1
  106. package/dist/cli/jailbreak-args.js +0 -86
  107. package/dist/cli/jailbreak-args.js.map +0 -1
  108. package/dist/commands/jailbreak.d.ts +0 -49
  109. package/dist/commands/jailbreak.d.ts.map +0 -1
  110. package/dist/commands/jailbreak.js +0 -201
  111. package/dist/commands/jailbreak.js.map +0 -1
  112. package/dist/encoding/index.d.ts +0 -6
  113. package/dist/encoding/index.d.ts.map +0 -1
  114. package/dist/encoding/index.js +0 -6
  115. package/dist/encoding/index.js.map +0 -1
  116. package/dist/encoding/l4-encoder.d.ts +0 -96
  117. package/dist/encoding/l4-encoder.d.ts.map +0 -1
  118. package/dist/encoding/l4-encoder.js +0 -474
  119. package/dist/encoding/l4-encoder.js.map +0 -1
  120. package/dist/fuzzing/index.d.ts +0 -6
  121. package/dist/fuzzing/index.d.ts.map +0 -1
  122. package/dist/fuzzing/index.js +0 -6
  123. package/dist/fuzzing/index.js.map +0 -1
  124. package/dist/fuzzing/mutation-engine.d.ts +0 -93
  125. package/dist/fuzzing/mutation-engine.d.ts.map +0 -1
  126. package/dist/fuzzing/mutation-engine.js +0 -244
  127. package/dist/fuzzing/mutation-engine.js.map +0 -1
  128. package/dist/jailbreak-extension.d.ts +0 -48
  129. package/dist/jailbreak-extension.d.ts.map +0 -1
  130. package/dist/jailbreak-extension.js +0 -47
  131. package/dist/jailbreak-extension.js.map +0 -1
  132. package/dist/modules/index.d.ts +0 -8
  133. package/dist/modules/index.d.ts.map +0 -1
  134. package/dist/modules/index.js +0 -7
  135. package/dist/modules/index.js.map +0 -1
  136. package/dist/modules/lrl-bypass.d.ts +0 -40
  137. package/dist/modules/lrl-bypass.d.ts.map +0 -1
  138. package/dist/modules/lrl-bypass.js +0 -84
  139. package/dist/modules/lrl-bypass.js.map +0 -1
  140. package/dist/modules/lrl-deps.d.ts +0 -38
  141. package/dist/modules/lrl-deps.d.ts.map +0 -1
  142. package/dist/modules/lrl-deps.js +0 -128
  143. package/dist/modules/lrl-deps.js.map +0 -1
  144. package/dist/multiturn/easl-chain.d.ts +0 -113
  145. package/dist/multiturn/easl-chain.d.ts.map +0 -1
  146. package/dist/multiturn/easl-chain.js +0 -351
  147. package/dist/multiturn/easl-chain.js.map +0 -1
  148. package/dist/multiturn/index.d.ts +0 -6
  149. package/dist/multiturn/index.d.ts.map +0 -1
  150. package/dist/multiturn/index.js +0 -6
  151. package/dist/multiturn/index.js.map +0 -1
  152. package/dist/routing/fallback-router.d.ts +0 -123
  153. package/dist/routing/fallback-router.d.ts.map +0 -1
  154. package/dist/routing/fallback-router.js +0 -355
  155. package/dist/routing/fallback-router.js.map +0 -1
  156. package/dist/routing/index.d.ts +0 -6
  157. package/dist/routing/index.d.ts.map +0 -1
  158. package/dist/routing/index.js +0 -6
  159. package/dist/routing/index.js.map +0 -1
  160. package/dist/types/jailbreak.d.ts +0 -246
  161. package/dist/types/jailbreak.d.ts.map +0 -1
  162. package/dist/types/jailbreak.js +0 -8
  163. package/dist/types/jailbreak.js.map +0 -1
  164. package/dist/utils/jailbreak/common.d.ts +0 -63
  165. package/dist/utils/jailbreak/common.d.ts.map +0 -1
  166. package/dist/utils/jailbreak/common.js +0 -130
  167. package/dist/utils/jailbreak/common.js.map +0 -1
  168. package/dist/utils/jailbreak/index.d.ts +0 -7
  169. package/dist/utils/jailbreak/index.d.ts.map +0 -1
  170. package/dist/utils/jailbreak/index.js +0 -7
  171. package/dist/utils/jailbreak/index.js.map +0 -1
@@ -0,0 +1,683 @@
1
+ /**
2
+ * Reasoning-router v4 — confidence-bearing classifier (Goal 009 Wave 1 Lane A,
3
+ * specs/008-reasoning-router-advanced-accuracy Requirement 2).
4
+ *
5
+ * v4 is a NEW, OPT-IN, PURE sibling module. It does not import from, and is not
6
+ * imported by, reasoning-router.ts (v1), reasoning-router-v2.ts,
7
+ * reasoning-router-v3.ts, reasoning-router-weights.ts, or agent-session.ts.
8
+ * Activating v4 in the product (a `/think auto-v4` mode, settings wiring, etc.)
9
+ * is explicitly out of scope for this lane and is left to a later,
10
+ * single-writer integration lane (specs/008 plan.md Wave 3).
11
+ *
12
+ * Deterministic by construction: same (input, weights) -> same
13
+ * ClassifierVerdictV4; same (verdict, availableLevels, laneType, bias, hint) ->
14
+ * same ThinkingLevel. No clock, randomness, I/O, model calls, network access,
15
+ * or state mutation anywhere in this file (ThinkingLevel is a type-only import
16
+ * and is erased).
17
+ *
18
+ * ============================================================================
19
+ * WHAT'S NEW VS v3 (reasoning-router-v3.ts)
20
+ * ============================================================================
21
+ * 1. VERDICT, NOT JUST A CLASS. `classifyTaskV4` returns a `ClassifierVerdictV4`
22
+ * carrying the full per-class score map, the runner-up class, the margin,
23
+ * a normalized confidence ratio, a confidence band, a `tieBreak` flag (the
24
+ * top class won only via precedence order over an exact score tie), a
25
+ * `fallbackReason` (non-null exactly when the zero-score cascade decided
26
+ * the class instead of a real signal), and `suppressedFeatureIds` (an
27
+ * audit trail of which whole-prompt signals were nulled by bounded
28
+ * negation). None of this carries prompt text — every field is a bounded
29
+ * enum, number, boolean, or a closed set of short diagnostic-id strings.
30
+ * 2. WEIGHTS TABLE, NOT LITERALS. Every score bump v3 hard-coded as an inline
31
+ * integer now reads from a `RouterWeightsV4` (see
32
+ * reasoning-router-v4-weights.ts). `DEFAULT_WEIGHTS_V4` reproduces v3's
33
+ * literals exactly, so classifyTaskV4(input, DEFAULT_WEIGHTS_V4).taskClass
34
+ * matches classifyTaskV3(input) on the full non-holdout GOLD_SET and every
35
+ * focused v3 sentinel (see the 013 regression test and this lane's
36
+ * evidence file). Passing a different `RouterWeightsV4` recalibrates
37
+ * without touching this file.
38
+ * 3. BOUNDED NEGATION. Every whole-prompt keyword/contextual pattern scan
39
+ * (the ones v3 ran as a bare `PATTERN.test(prompt)`) is now negation-aware:
40
+ * a match is only counted if no negation cue (don't/doesn't/isn't/never/
41
+ * avoid/skip/without/instead of/rather than/no need to|for/not a/...) appears within
42
+ * `weights.negationWindowChars` characters immediately before it, and the
43
+ * scan never crosses a `.,;!?` clause boundary. Patterns anchored to the
44
+ * START of the leading clause (hasLeadingDebugAction, hasLeadingReviewIntent,
45
+ * etc.) are untouched and need no negation handling: "don't refactor" can
46
+ * never match an anchored `^refactor` test in the first place. Concrete
47
+ * fix: "don't refactor this, just fix the crash" now classifies as debug
48
+ * (v3 misclassifies this as refactor; see the 013 test).
49
+ * 4. BOUNDED COMPOUND INTENT. A short-range conjunction split (bare "then",
50
+ * "and then", "and also", or ";", only within the first 300 characters)
51
+ * looks for a second, distinct leading-verb intent after the split point.
52
+ * When found and distinct from the primary leading intent, it contributes
53
+ * a bounded `secondClauseIntent` bump and sets `compoundIntent: true` on
54
+ * the verdict. This is proven inert on every current GOLD_SET row (no
55
+ * gold-set prompt produces a non-null, distinct second-clause intent), so
56
+ * it never changes an existing classification under DEFAULT_WEIGHTS_V4 —
57
+ * it only sharpens confidence/margin metadata for genuinely compound
58
+ * prompts and gives future calibration a lever.
59
+ * 5. CONFIDENCE NEVER LOWERS EFFORT. `resolveThinkingLevelV4WithUncertainty`
60
+ * computes the exact same base target as v2/v3's resolvers (rule table +
61
+ * lane step + bias + hint), then ONLY ADDS a bounded escalation step when
62
+ * confidence is low or the class came from the fallback cascade. A prompt
63
+ * like "don't think hard, just fix the crash" cannot use its own text to
64
+ * talk the resolver down: the escalation term is strictly non-negative, so
65
+ * the output is always >= what the same taskClass would resolve to via
66
+ * `resolveThinkingLevelV4ForAuto` (mirrors spec 008 Req 2 acceptance
67
+ * criterion "confidence cannot lower effort").
68
+ * ============================================================================
69
+ */
70
+ import { DEFAULT_WEIGHTS_V4, TASK_CLASSES_V4, } from "./reasoning-router-v4-weights.js";
71
+ export { DEFAULT_WEIGHTS_V4, TASK_CLASSES_V4 } from "./reasoning-router-v4-weights.js";
72
+ /** Prompts shorter than this (trimmed) fall back to trivial when no real signal scores > 0. */
73
+ const TRIVIAL_MAX_CHARS_V4 = 40;
74
+ /** Prompts at/above this length with no real signal fall back to plan (long prose brief). */
75
+ const COMPLEX_PROSE_MIN_CHARS_V4 = 2400;
76
+ /** Plan-brief prompts at/above this length count as a "long brief" even without PLAN_BRIEF_PATTERN. */
77
+ const LONG_BRIEF_MIN_CHARS_V4 = 512;
78
+ /** Compound-clause split only applies within this many leading characters (short direct commands, not long prose). */
79
+ const COMPOUND_SPLIT_MAX_INDEX_V4 = 300;
80
+ /** Minimum trimmed length of a candidate second clause to be worth checking for a leading intent. */
81
+ const COMPOUND_SECOND_CLAUSE_MIN_CHARS_V4 = 3;
82
+ /** Bounded look-ahead cap when checking a second clause's leading intent (defense in depth; independent of regex cost). */
83
+ const COMPOUND_SECOND_CLAUSE_SCAN_CHARS_V4 = 200;
84
+ /** Lane fallback class, used only when every scored class is <= 0 (zero-score cascade). Identical to v1/v2/v3. */
85
+ const LANE_FALLBACK_CLASS_V4 = {
86
+ planner: "plan",
87
+ security: "review",
88
+ explorer: "review",
89
+ coder: "code-gen",
90
+ reviewer: "review",
91
+ tester: "code-gen",
92
+ };
93
+ // ============================================================================
94
+ // Whole-prompt / leading-clause patterns (copied verbatim from
95
+ // reasoning-router-v3.ts; identical regex source so DEFAULT_WEIGHTS_V4
96
+ // reproduces v3 exactly whenever no negation cue is present).
97
+ // ============================================================================
98
+ 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;
99
+ 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;
100
+ 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;
101
+ 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;
102
+ const BUG_OBJECT_PATTERN = /\b(null\s+pointer|null\s+deref|race(?:\s+condition)?|heap(?:\s+overflow)?|use-after-free|memory\s+leak|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;
103
+ const NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN = /\berror\s+(?:handling|messages?|budgets?)\b|에러\s*처리|오류\s*처리/i;
104
+ const GENERIC_DIAGNOSTIC_PATTERN = /\b(errors?|broken|wrong\s+results|rolls?\s+back|rollback)\b/i;
105
+ 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;
106
+ const OPERATIONAL_RUNBOOK_SIGNAL_PATTERNS = [
107
+ { pattern: /\b(?:commit|commits|committed|committing)\b|커밋/i, critical: false },
108
+ { pattern: /\b(?:push|pushed|pushing)\b|푸시|푸쉬/i, critical: true },
109
+ {
110
+ pattern: /\b(?:release|releases|tag|version\s+bump|bump\s+(?:the\s+)?version)\b|릴리즈|태그|버전/i,
111
+ critical: true,
112
+ },
113
+ { pattern: /\bnpm\s+publish\b|\bpublish(?:ing|ed)?\b|퍼블리시|배포/i, critical: true },
114
+ { pattern: /\b(?:CHANGELOG\.md|changelog|release\s+notes?)\b|채널로그|체인지로그/i, critical: false },
115
+ { pattern: /\bREADME\.md\b|\breadme\b/i, critical: false },
116
+ { pattern: /\bci\s*\/\s*cd\b|\bgithub\s+actions?\b|\bworkflow\b|깃허브\s*액션|깃헙\s*액션/i, critical: false },
117
+ ];
118
+ 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;
119
+ 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;
120
+ const REFACTOR_CUE_PATTERN = /\b(refactor(?:ing|ed)?|extract|rename|deduplicate|consolidate|modularize|reorganize|simplify|clean\s*up|restructure|split\s+module|move\s+logic|untangle|merge\s+duplicate)\b|리팩토링(?!하지\s*마|하지\s*말)|리팩터링(?!하지\s*마|하지\s*말)|구조\s*개선/i;
121
+ const ADD_KEYWORD_PATTERN = /\badd\b/i;
122
+ const LOW_RISK_EDIT_ACTION_PATTERN = /^(?:correct|update|swap|remove|reword|tweak|bump|trim)\b/i;
123
+ /** Whole-prompt keyword-family patterns (mirrors v3's KEYWORD_PATTERNS_V3; refactor/simple-edit reuse the patterns above). */
124
+ const DEBUG_KEYWORD_FAMILY_PATTERN = /^(?:debug|investigate\s+why|reproduce|trace\b)|\bfix\s+this\s+(?:traceback|panic|error)\b|디버깅|디버그|재현|원인\s*분석/i;
125
+ const REVIEW_KEYWORD_FAMILY_PATTERN = /\b(review|critique|assess|inspect|approve|lgtm|double-?check|audit(?!\s+log\b))\b|리뷰|검토|점검/i;
126
+ const PLAN_KEYWORD_FAMILY_PATTERN = /\b(plan|design|architect|architecture|roadmap|spec(?:ification)?|strategy|decompose|milestones?|write\s+a\s+(?:technical\s+)?spec|create\s+a\s+(?:roadmap|strategy|plan))\b|설계|로드맵|아키텍처|계획\s*세워|기획/i;
127
+ const CODE_GEN_KEYWORD_FAMILY_PATTERN = /\b(implement|write|create|build|generate|scaffold|prototype)\b|구현|만들어\s*줘|생성|작성|추가해/i;
128
+ const KO_SHORT_DEBUG_SIGNAL_PATTERN_V4 = /오류|에러|실패|안\s*돼|안\s*됨|깨졌|깨짐|고장|디버깅|디버그|재현|원인\s*(?:분석|파악)?/i;
129
+ const KO_SHORT_SIMPLE_EDIT_SIGNAL_PATTERN_V4 = /오타|맞춤법|띄어쓰기|문구|제목/i;
130
+ const KO_SHORT_REVIEW_SIGNAL_PATTERN_V4 = /리뷰|검토|점검/i;
131
+ const KO_SHORT_PLAN_SIGNAL_PATTERN_V4 = /설계|로드맵|아키텍처|계획\s*세워|기획/i;
132
+ const KO_SHORT_REFACTOR_SIGNAL_PATTERN_V4 = /리팩토링|리팩터링|구조\s*개선/i;
133
+ const KO_SHORT_CODE_GEN_SIGNAL_PATTERN_V4 = /구현|만들|생성|작성|추가|테스트|삭제|지워|지우|제거|고쳐|고치|수정|변경|바꿔|바꾸|옮겨|옮기|넣어/i;
134
+ /** Short-range compound-clause conjunction boundary. Only bare/compound "then"/"and also"/";" — never bare "and". */
135
+ const COMPOUND_SPLIT_PATTERN = /\band\s+then\b|\bthen\b|\band\s+also\b|;/i;
136
+ /**
137
+ * Bounded negation-cue vocabulary (spec 008 plan.md "bounded negation window").
138
+ * Deliberately excludes bare "not" (too broad; verified against the current
139
+ * GOLD_SET that every listed cue here is either absent or occurs AFTER, not
140
+ * before, a tracked keyword — see this lane's evidence file for the audit).
141
+ */
142
+ 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;
143
+ const DIRECT_NOT_OBJECT_CUE_PATTERN_V4 = /\b(?:not|no)\s+(?:a\s+|an\s+|the\s+)?$/i;
144
+ 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;
145
+ 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;
146
+ 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;
147
+ /** A closed set of clause-boundary characters; a negation cue never reaches across one of these into a prior clause. */
148
+ const CLAUSE_BOUNDARY_CHARS = [".", "!", "?", ";", ","];
149
+ function hasCodeFence(text) {
150
+ return text.includes("```");
151
+ }
152
+ /** Identical to v1/v2/v3: explicit hunk headers or `diff --git` count alone; bare +/- only count together. */
153
+ function hasDiffMarkers(text) {
154
+ if (/^@@[^\n]*@@/m.test(text) || /^diff --git /m.test(text))
155
+ return true;
156
+ return /^\+(?!\+)/m.test(text) && /^-(?!-)/m.test(text);
157
+ }
158
+ /** Identical to v3: first line (after stripping one polite prefix), capped at 180 chars. */
159
+ function firstClause(prompt) {
160
+ const firstLine = prompt.replace(/^(?:please|pls|can you|could you|would you|help me|i need you to)\s+/i, "").split("\n")[0] ?? "";
161
+ return firstLine.slice(0, 180);
162
+ }
163
+ // --- Leading-clause intent tests. All are `^`-anchored against the LEADING
164
+ // clause text only, so a negation cue at the start ("don't refactor...") can
165
+ // never match one of these: none of the alternations include "don't"/"never"/
166
+ // etc, so they are negation-immune by construction and are left unchanged
167
+ // from v3. ---
168
+ function hasLeadingReviewIntent(text) {
169
+ return /^(?:review|audit(?!\s+log\b)|critique|inspect|assess|approve|double-?check|lgtm)\b/i.test(text);
170
+ }
171
+ function hasLeadingPlanIntent(text) {
172
+ return /^(?:plan|design|architect|decompose)\b|^(?:write|create)\s+(?:a\s+)?(?:technical\s+)?(?:spec|roadmap|strategy|plan)\b/i.test(text);
173
+ }
174
+ function hasLeadingRefactorIntent(text) {
175
+ 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);
176
+ }
177
+ function hasLeadingDebugAction(text) {
178
+ return /^(?:debug|investigate\s+why|reproduce|trace\b)|^fix\s+this\s+(?:traceback|panic|error)\b/i.test(text);
179
+ }
180
+ function hasLeadingCodeGenIntent(text) {
181
+ return /^(?:implement|write|create|build|generate|scaffold|prototype|add)\b/i.test(text);
182
+ }
183
+ function hasLeadingSimpleEditIntent(text) {
184
+ return /^(?:correct|update|swap|remove|adjust|fix|add|change|trim|reword|tweak|bump)\b/i.test(text);
185
+ }
186
+ const NO_MATCH_RESULT_V4 = { matched: false, suppressed: false };
187
+ function matchOperationalRunbookV4(prompt, windowChars) {
188
+ let distinctSignals = 0;
189
+ let criticalSignals = 0;
190
+ let sawSuppressed = false;
191
+ for (const { pattern, critical } of OPERATIONAL_RUNBOOK_SIGNAL_PATTERNS) {
192
+ const result = matchUnnegated(prompt, pattern, windowChars);
193
+ if (result.matched) {
194
+ distinctSignals += 1;
195
+ if (critical)
196
+ criticalSignals += 1;
197
+ }
198
+ if (result.suppressed)
199
+ sawSuppressed = true;
200
+ }
201
+ const explicitRelease = matchUnnegated(prompt, EXPLICIT_RELEASE_RUNBOOK_PATTERN, windowChars).matched;
202
+ const matched = explicitRelease || distinctSignals >= 3 || (criticalSignals >= 2 && distinctSignals >= 2);
203
+ return { matched, suppressed: !matched && sawSuppressed };
204
+ }
205
+ /**
206
+ * Negation-aware replacement for v3's bare `pattern.test(prompt)`. Finds every
207
+ * occurrence of `pattern` in `prompt`; for each, looks back up to
208
+ * `windowChars` characters (never crossing a `.,;!?` boundary into a prior
209
+ * clause) for a `NEGATION_CUE_PATTERN` hit, and looks forward inside the same
210
+ * bounded window for Hangul-only post-positioned negation cues such as
211
+ * "하지 말고". `matched` is true iff at least one occurrence survives
212
+ * un-negated. Deterministic, single pass per pattern, no shared regex state (a
213
+ * fresh global-flag RegExp is constructed per call).
214
+ */
215
+ function hasPrePositionedNegationCueV4(scoped) {
216
+ if (!NEGATION_CUE_PATTERN.test(scoped) && !DIRECT_NOT_OBJECT_CUE_PATTERN_V4.test(scoped))
217
+ return false;
218
+ return !DOUBLE_NEGATION_RESCUE_PATTERN_V4.test(scoped) && !DELIBERATIVE_QUESTION_RESCUE_PATTERN_V4.test(scoped);
219
+ }
220
+ function hasPostPositionedNegationCueV4(prompt, matchEnd, windowChars) {
221
+ const scoped = prompt.slice(matchEnd, Math.min(prompt.length, matchEnd + windowChars));
222
+ return POSTPOSITIONED_NEGATION_CUE_PATTERN_V4.test(scoped);
223
+ }
224
+ function matchUnnegated(prompt, pattern, windowChars) {
225
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
226
+ const globalPattern = new RegExp(pattern.source, flags);
227
+ let sawMatch = false;
228
+ let sawUnnegated = false;
229
+ let match = globalPattern.exec(prompt);
230
+ while (match !== null) {
231
+ sawMatch = true;
232
+ const matchStart = match.index;
233
+ const windowStart = Math.max(0, matchStart - windowChars);
234
+ const windowText = prompt.slice(windowStart, matchStart);
235
+ let boundary = -1;
236
+ for (const boundaryChar of CLAUSE_BOUNDARY_CHARS) {
237
+ const idx = windowText.lastIndexOf(boundaryChar);
238
+ if (idx > boundary)
239
+ boundary = idx;
240
+ }
241
+ const scoped = boundary >= 0 ? windowText.slice(boundary + 1) : windowText;
242
+ const preNegated = hasPrePositionedNegationCueV4(scoped);
243
+ const postNegated = !preNegated && hasPostPositionedNegationCueV4(prompt, matchStart + match[0].length, windowChars);
244
+ if (!preNegated && !postNegated)
245
+ sawUnnegated = true;
246
+ if (match[0].length === 0)
247
+ globalPattern.lastIndex += 1;
248
+ match = globalPattern.exec(prompt);
249
+ }
250
+ return { matched: sawUnnegated, suppressed: sawMatch && !sawUnnegated };
251
+ }
252
+ // ============================================================================
253
+ // Bounded compound-intent detection
254
+ // ============================================================================
255
+ /**
256
+ * Splits off a bounded second clause after a short-range conjunction
257
+ * ("then"/"and then"/"and also"/";"), only when the split point is within the
258
+ * first `COMPOUND_SPLIT_MAX_INDEX_V4` characters (short direct commands, not
259
+ * long prose briefs — verified this never fires inside the GOLD_SET's
260
+ * >=2400-char plan entries). Returns null when no qualifying split exists.
261
+ */
262
+ function splitCompoundClauseV4(prompt) {
263
+ const match = COMPOUND_SPLIT_PATTERN.exec(prompt);
264
+ if (match === null || match.index > COMPOUND_SPLIT_MAX_INDEX_V4)
265
+ return null;
266
+ const second = prompt.slice(match.index + match[0].length).trim();
267
+ if (second.length < COMPOUND_SECOND_CLAUSE_MIN_CHARS_V4)
268
+ return null;
269
+ return second;
270
+ }
271
+ /**
272
+ * Leading-intent test for a compound prompt's SECOND clause. Reuses the same
273
+ * `^`-anchored leading-intent tests as the primary clause (so it is equally
274
+ * negation-immune), without the primary clause's diagnosticEvidence/localEdit
275
+ * gating (a short second-clause fragment carries no such context of its own).
276
+ */
277
+ function secondClauseLeadingIntentV4(clause) {
278
+ const bounded = clause.slice(0, COMPOUND_SECOND_CLAUSE_SCAN_CHARS_V4);
279
+ return leadingIntentFromLeadingClauseV4(bounded);
280
+ }
281
+ function leadingIntentFromLeadingClauseV4(leading) {
282
+ if (hasLeadingDebugAction(leading))
283
+ return "debug";
284
+ if (hasLeadingReviewIntent(leading))
285
+ return "review";
286
+ if (hasLeadingPlanIntent(leading))
287
+ return "plan";
288
+ if (hasLeadingRefactorIntent(leading))
289
+ return "refactor";
290
+ if (hasLeadingSimpleEditIntent(leading))
291
+ return "simple-edit";
292
+ if (hasLeadingCodeGenIntent(leading))
293
+ return "code-gen";
294
+ return null;
295
+ }
296
+ function leadingIntentIsPostNegatedV4(leading, windowChars) {
297
+ 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);
298
+ return (leadingIntentMatch !== null && hasPostPositionedNegationCueV4(leading, leadingIntentMatch[0].length, windowChars));
299
+ }
300
+ function classifyShortKoreanZeroScoreTaskV4(prompt, windowChars) {
301
+ if (matchUnnegated(prompt, KO_SHORT_DEBUG_SIGNAL_PATTERN_V4, windowChars).matched)
302
+ return "debug";
303
+ if (matchUnnegated(prompt, KO_SHORT_SIMPLE_EDIT_SIGNAL_PATTERN_V4, windowChars).matched)
304
+ return "simple-edit";
305
+ if (matchUnnegated(prompt, KO_SHORT_REVIEW_SIGNAL_PATTERN_V4, windowChars).matched)
306
+ return "review";
307
+ if (matchUnnegated(prompt, KO_SHORT_PLAN_SIGNAL_PATTERN_V4, windowChars).matched)
308
+ return "plan";
309
+ if (matchUnnegated(prompt, KO_SHORT_REFACTOR_SIGNAL_PATTERN_V4, windowChars).matched)
310
+ return "refactor";
311
+ if (matchUnnegated(prompt, KO_SHORT_CODE_GEN_SIGNAL_PATTERN_V4, windowChars).matched)
312
+ return "code-gen";
313
+ return null;
314
+ }
315
+ /** Identical decision order to v3's `leadingIntent`. */
316
+ function leadingIntentV4(input) {
317
+ if (input.diagnosticEvidence &&
318
+ (hasLeadingDebugAction(input.firstClause) || hasLeadingSimpleEditIntent(input.firstClause)))
319
+ return "debug";
320
+ if (hasLeadingReviewIntent(input.firstClause))
321
+ return "review";
322
+ if (hasLeadingPlanIntent(input.firstClause))
323
+ return "plan";
324
+ if (hasLeadingRefactorIntent(input.firstClause))
325
+ return "refactor";
326
+ if (input.localEdit && hasLeadingSimpleEditIntent(input.firstClause))
327
+ return "simple-edit";
328
+ if (!input.localEdit && hasLeadingCodeGenIntent(input.firstClause))
329
+ return "code-gen";
330
+ return null;
331
+ }
332
+ /** Identical decision structure to v3's `hasDiagnosticEvidence`, parameterized over precomputed negation-aware matches. */
333
+ function hasDiagnosticEvidenceV4(leading, hardDiagnosticMatched, bugObjectMatched, genericDiagnosticMatched, nonDiagnosticContext) {
334
+ if (hasLeadingDebugAction(leading) || hardDiagnosticMatched)
335
+ return true;
336
+ const hasContextualDiagnostic = bugObjectMatched || (genericDiagnosticMatched && !nonDiagnosticContext);
337
+ if (!hasContextualDiagnostic)
338
+ return false;
339
+ return !hasLeadingReviewIntent(leading) && !hasLeadingPlanIntent(leading) && !hasLeadingRefactorIntent(leading);
340
+ }
341
+ /**
342
+ * Extracts every v4 feature from one prompt, negation-gating every
343
+ * whole-prompt scan exactly once and recording a `negation:<channel>` id in
344
+ * `suppressed` whenever a match existed but every occurrence was negated.
345
+ */
346
+ function extractFeaturesV4(prompt, weights, suppressed) {
347
+ const leading = firstClause(prompt);
348
+ const window = weights.negationWindowChars;
349
+ const implementationObjectResult = matchUnnegated(prompt, IMPLEMENTATION_OBJECT_PATTERN, window);
350
+ if (implementationObjectResult.suppressed)
351
+ suppressed.push("negation:implementation-object");
352
+ const implementationObject = implementationObjectResult.matched;
353
+ const lowRiskEditAction = LOW_RISK_EDIT_ACTION_PATTERN.test(leading);
354
+ const localEditObjectResult = matchUnnegated(prompt, LOCAL_EDIT_OBJECT_PATTERN, window);
355
+ if (localEditObjectResult.suppressed)
356
+ suppressed.push("negation:local-edit-object");
357
+ const localEditActionResult = matchUnnegated(prompt, LOCAL_EDIT_ACTION_OBJECT_PATTERN, window);
358
+ if (localEditActionResult.suppressed)
359
+ suppressed.push("negation:local-edit-action-object");
360
+ const localEdit = !implementationObject && (lowRiskEditAction || localEditObjectResult.matched || localEditActionResult.matched);
361
+ const hardDiagnosticResult = matchUnnegated(prompt, HARD_DIAGNOSTIC_PATTERN, window);
362
+ if (hardDiagnosticResult.suppressed)
363
+ suppressed.push("negation:hard-diagnostic");
364
+ const bugObjectResult = matchUnnegated(prompt, BUG_OBJECT_PATTERN, window);
365
+ if (bugObjectResult.suppressed)
366
+ suppressed.push("negation:bug-object");
367
+ const genericDiagnosticResult = matchUnnegated(prompt, GENERIC_DIAGNOSTIC_PATTERN, window);
368
+ const nonDiagnosticContext = NON_DIAGNOSTIC_DEBUG_CONTEXT_PATTERN.test(prompt);
369
+ if (genericDiagnosticResult.suppressed && !nonDiagnosticContext)
370
+ suppressed.push("negation:generic-diagnostic");
371
+ const diagnosticEvidence = hasDiagnosticEvidenceV4(leading, hardDiagnosticResult.matched, bugObjectResult.matched, genericDiagnosticResult.matched, nonDiagnosticContext);
372
+ const reviewScopeResult = matchUnnegated(prompt, REVIEW_SCOPE_PATTERN, window);
373
+ if (reviewScopeResult.suppressed)
374
+ suppressed.push("negation:review-scope");
375
+ const reviewScope = hasLeadingReviewIntent(leading) && reviewScopeResult.matched;
376
+ const planBriefResult = matchUnnegated(prompt, PLAN_BRIEF_PATTERN, window);
377
+ if (planBriefResult.suppressed)
378
+ suppressed.push("negation:plan-brief");
379
+ const planBrief = hasLeadingPlanIntent(leading) && (planBriefResult.matched || prompt.length >= LONG_BRIEF_MIN_CHARS_V4);
380
+ const operationalRunbookResult = matchOperationalRunbookV4(prompt, window);
381
+ if (operationalRunbookResult.suppressed)
382
+ suppressed.push("negation:operational-runbook");
383
+ const operationalRunbook = operationalRunbookResult.matched;
384
+ const refactorCueResult = matchUnnegated(prompt, REFACTOR_CUE_PATTERN, window);
385
+ if (refactorCueResult.suppressed)
386
+ suppressed.push("negation:refactor-cue");
387
+ const refactorCue = refactorCueResult.matched;
388
+ const rawPrimaryIntent = leadingIntentV4({ firstClause: leading, localEdit, diagnosticEvidence });
389
+ const primaryIntent = rawPrimaryIntent !== null && leadingIntentIsPostNegatedV4(leading, window) ? null : rawPrimaryIntent;
390
+ const secondClauseText = splitCompoundClauseV4(prompt);
391
+ const secondClauseIntent = secondClauseText === null ? null : secondClauseLeadingIntentV4(secondClauseText);
392
+ const compoundIntent = secondClauseIntent !== null && secondClauseIntent !== primaryIntent;
393
+ const debugKeywordResult = diagnosticEvidence
394
+ ? matchUnnegated(prompt, DEBUG_KEYWORD_FAMILY_PATTERN, window)
395
+ : NO_MATCH_RESULT_V4;
396
+ if (debugKeywordResult.suppressed)
397
+ suppressed.push("negation:keyword-debug");
398
+ const reviewKeywordResult = matchUnnegated(prompt, REVIEW_KEYWORD_FAMILY_PATTERN, window);
399
+ if (reviewKeywordResult.suppressed)
400
+ suppressed.push("negation:keyword-review");
401
+ const planKeywordResult = matchUnnegated(prompt, PLAN_KEYWORD_FAMILY_PATTERN, window);
402
+ if (planKeywordResult.suppressed)
403
+ suppressed.push("negation:keyword-plan");
404
+ const codeGenKeywordResult = matchUnnegated(prompt, CODE_GEN_KEYWORD_FAMILY_PATTERN, window);
405
+ if (codeGenKeywordResult.suppressed)
406
+ suppressed.push("negation:keyword-code-gen");
407
+ const addKeywordResult = localEdit ? NO_MATCH_RESULT_V4 : matchUnnegated(prompt, ADD_KEYWORD_PATTERN, window);
408
+ if (addKeywordResult.suppressed)
409
+ suppressed.push("negation:add-keyword");
410
+ return {
411
+ firstClause: leading,
412
+ codeFence: hasCodeFence(prompt),
413
+ diffHunk: hasDiffMarkers(prompt),
414
+ localEdit,
415
+ diagnosticEvidence,
416
+ reviewScope,
417
+ planBrief,
418
+ operationalRunbook,
419
+ refactorCue,
420
+ implementationObject,
421
+ leadingIntent: primaryIntent,
422
+ secondClauseIntent,
423
+ compoundIntent,
424
+ keywordFamilyMatch: {
425
+ trivial: false,
426
+ debug: debugKeywordResult.matched,
427
+ refactor: refactorCue,
428
+ review: reviewKeywordResult.matched,
429
+ plan: planKeywordResult.matched,
430
+ "simple-edit": localEditObjectResult.matched,
431
+ "code-gen": codeGenKeywordResult.matched,
432
+ },
433
+ addKeywordMatch: addKeywordResult.matched,
434
+ };
435
+ }
436
+ // ============================================================================
437
+ // Scoring
438
+ // ============================================================================
439
+ function emptyScoresV4() {
440
+ return {
441
+ trivial: 0,
442
+ "simple-edit": 0,
443
+ "code-gen": 0,
444
+ debug: 0,
445
+ refactor: 0,
446
+ review: 0,
447
+ plan: 0,
448
+ };
449
+ }
450
+ function computeScoresV4(features, weights) {
451
+ const scores = emptyScoresV4();
452
+ if (features.codeFence || features.diffHunk)
453
+ scores["code-gen"] += weights.codeFenceOrDiff;
454
+ if (features.leadingIntent !== null)
455
+ scores[features.leadingIntent] += weights.leadingIntent;
456
+ if (features.secondClauseIntent !== null)
457
+ scores[features.secondClauseIntent] += weights.secondClauseIntent;
458
+ if (features.localEdit)
459
+ scores["simple-edit"] += weights.localEdit;
460
+ if (features.diagnosticEvidence)
461
+ scores.debug += weights.diagnosticEvidence;
462
+ if (features.reviewScope)
463
+ scores.review += weights.reviewScope;
464
+ if (features.planBrief)
465
+ scores.plan += weights.planBrief;
466
+ if (features.operationalRunbook)
467
+ scores.plan += weights.operationalRunbook;
468
+ if (features.refactorCue)
469
+ scores.refactor += weights.refactorCue;
470
+ if (features.implementationObject)
471
+ scores["code-gen"] += weights.implementationObject;
472
+ for (const taskClass of TASK_CLASSES_V4) {
473
+ if (features.keywordFamilyMatch[taskClass])
474
+ scores[taskClass] += weights.keywordFamily[taskClass];
475
+ }
476
+ if (features.addKeywordMatch)
477
+ scores["code-gen"] += weights.addKeyword;
478
+ return scores;
479
+ }
480
+ /**
481
+ * Extension signals (history / context-pressure / judge vote). Every
482
+ * coefficient is 0 under DEFAULT_WEIGHTS_V4 (inert until calibrated by a
483
+ * future governance-backed lane); the mechanism exists so
484
+ * `TaskClassifierInputV4`'s optional fields are meaningfully wired rather than
485
+ * silently accepted-and-ignored (as they are in v3's identical-shaped input).
486
+ */
487
+ function applyExtensionSignalsV4(scores, input, weights) {
488
+ const priorClass = input.history !== undefined && input.history.length > 0 ? input.history[0] : null;
489
+ if (priorClass !== null && weights.multiTurnPrior !== 0)
490
+ scores[priorClass] += weights.multiTurnPrior;
491
+ const judgeVote = input.judgeVote ?? null;
492
+ if (judgeVote !== null && weights.judgeVote !== 0)
493
+ scores[judgeVote] += weights.judgeVote;
494
+ const pressureBucket = input.pressureBucket ?? 0;
495
+ if (pressureBucket > 0 && weights.pressureBucket !== 0) {
496
+ const bump = pressureBucket * weights.pressureBucket;
497
+ scores.debug += bump;
498
+ scores.review += bump;
499
+ scores.plan += bump;
500
+ }
501
+ }
502
+ // ============================================================================
503
+ // Classifier
504
+ // ============================================================================
505
+ /**
506
+ * Deterministic confidence-bearing classifier (spec 008 Req 2).
507
+ *
508
+ * Pipeline: extract negation/compound-aware contextual features -> score every
509
+ * class from `weights` -> argmax with `TASK_CLASSES_V4` precedence tie-break,
510
+ * tracking the runner-up -> compute margin/confidence/confidenceBand/tieBreak
511
+ * from the RAW scores -> if `topScore <= 0`, replace `taskClass` (and record
512
+ * `fallbackReason`) via the same zero-score cascade v1/v2/v3 use (fence/diff
513
+ * -> code-gen; length < 40 -> trivial; length >= 2400 -> plan; lane fallback;
514
+ * default code-gen) — the raw scores/margin/runnerUp/tieBreak fields still
515
+ * reflect the pre-cascade computation, for audit purposes.
516
+ *
517
+ * Pure: same (input, weights) -> same ClassifierVerdictV4.
518
+ */
519
+ export function classifyTaskV4(input, weights = DEFAULT_WEIGHTS_V4) {
520
+ const prompt = input.prompt.trim();
521
+ const suppressed = [];
522
+ const features = extractFeaturesV4(prompt, weights, suppressed);
523
+ const scores = computeScoresV4(features, weights);
524
+ applyExtensionSignalsV4(scores, input, weights);
525
+ let top = TASK_CLASSES_V4[0];
526
+ for (const c of TASK_CLASSES_V4) {
527
+ if (scores[c] > scores[top])
528
+ top = c;
529
+ }
530
+ let runnerUp = null;
531
+ for (const c of TASK_CLASSES_V4) {
532
+ if (c === top)
533
+ continue;
534
+ if (runnerUp === null || scores[c] > scores[runnerUp])
535
+ runnerUp = c;
536
+ }
537
+ const topScore = scores[top];
538
+ const runnerUpScore = runnerUp !== null ? scores[runnerUp] : 0;
539
+ const margin = topScore - runnerUpScore;
540
+ const tieBreak = runnerUp !== null && topScore === runnerUpScore;
541
+ const confidence = topScore <= 0 ? 0 : Math.max(0, Math.min(1, margin / topScore));
542
+ const confidenceBand = confidence <= weights.lowConfidenceThreshold
543
+ ? "low"
544
+ : confidence >= weights.highConfidenceThreshold
545
+ ? "high"
546
+ : "medium";
547
+ let taskClass = top;
548
+ let fallbackReason = null;
549
+ if (topScore <= 0) {
550
+ if (features.codeFence || features.diffHunk) {
551
+ taskClass = "code-gen";
552
+ fallbackReason = "code-fence-or-diff";
553
+ }
554
+ else if (prompt.length < TRIVIAL_MAX_CHARS_V4) {
555
+ const shortKoreanTaskClass = classifyShortKoreanZeroScoreTaskV4(prompt, weights.negationWindowChars);
556
+ if (shortKoreanTaskClass !== null) {
557
+ taskClass = shortKoreanTaskClass;
558
+ fallbackReason = "ko-short-task-signal";
559
+ }
560
+ else {
561
+ taskClass = "trivial";
562
+ fallbackReason = "trivial-length";
563
+ }
564
+ }
565
+ else if (prompt.length >= COMPLEX_PROSE_MIN_CHARS_V4) {
566
+ taskClass = "plan";
567
+ fallbackReason = "long-prose";
568
+ }
569
+ else if (input.laneType !== undefined) {
570
+ taskClass = LANE_FALLBACK_CLASS_V4[input.laneType];
571
+ fallbackReason = "lane-fallback";
572
+ }
573
+ else {
574
+ taskClass = "code-gen";
575
+ fallbackReason = "default";
576
+ }
577
+ }
578
+ return {
579
+ taskClass,
580
+ scores,
581
+ runnerUp,
582
+ margin,
583
+ confidence,
584
+ confidenceBand,
585
+ tieBreak,
586
+ fallbackReason,
587
+ suppressedFeatureIds: suppressed,
588
+ compoundIntent: features.compoundIntent,
589
+ secondClauseIntent: features.secondClauseIntent,
590
+ };
591
+ }
592
+ // ============================================================================
593
+ // Resolver
594
+ // ============================================================================
595
+ /** Reasoning ladder used for targets and clamping. Intentionally excludes "off". Identical to v1/v2/v3. */
596
+ const REASONING_LADDER_V4 = ["minimal", "low", "medium", "high", "xhigh", "max"];
597
+ /** Static rule table: task class -> recommended ThinkingLevel. Identical to v1/v2/v3. */
598
+ export const TASK_CLASS_THINKING_LEVELS_V4 = {
599
+ trivial: "minimal",
600
+ "simple-edit": "low",
601
+ "code-gen": "medium",
602
+ debug: "high",
603
+ refactor: "high",
604
+ review: "high",
605
+ plan: "xhigh",
606
+ };
607
+ /** Lane ladder adjustment. Identical to v1/v2/v3: planner/security escalate one step, explorer de-escalates. */
608
+ const LANE_STEP_V4 = {
609
+ planner: 1,
610
+ security: 1,
611
+ explorer: -1,
612
+ };
613
+ /** Maximum magnitude of the learning/consult bias (ladder steps). Identical bound to v2/v3. */
614
+ const BIAS_MAX_V4 = 2;
615
+ /** Confidence floor at which an override hint is fused into the target. Identical to v2/v3. */
616
+ const HINT_CONFIDENCE_THRESHOLD_V4 = 0.7;
617
+ /**
618
+ * Bounded, strictly non-negative escalation applied when the verdict's own
619
+ * confidence is low or a fallback (not a real signal) decided the class. This
620
+ * is the ONLY place confidence touches the target index, and it can only add:
621
+ * confidence can never lower effort (spec 008 Req 2 acceptance criterion).
622
+ */
623
+ const LOW_CONFIDENCE_ESCALATION_STEPS_V4 = 1;
624
+ function clampToLadderIndexV4(index) {
625
+ return Math.max(0, Math.min(index, REASONING_LADDER_V4.length - 1));
626
+ }
627
+ function clampToAvailableV4(targetIndex, availableLevels) {
628
+ const availableOnLadder = REASONING_LADDER_V4.filter((level) => availableLevels.includes(level));
629
+ if (availableOnLadder.length === 0)
630
+ return availableLevels[0] ?? "off";
631
+ for (let i = targetIndex; i >= 0; i--) {
632
+ const candidate = REASONING_LADDER_V4[i];
633
+ if (availableOnLadder.includes(candidate))
634
+ return candidate;
635
+ }
636
+ return availableOnLadder[0];
637
+ }
638
+ /**
639
+ * Thin auto-mode wrapper with no uncertainty adjustment: base rule table ->
640
+ * lane step -> clamp to availableLevels. Equivalent to
641
+ * `resolveThinkingLevelV3ForAuto` / `resolveThinkingLevelV2ForAuto` for the
642
+ * same (taskClass, availableLevels, laneType) — i.e. v4's "confident" path.
643
+ */
644
+ export function resolveThinkingLevelV4ForAuto(taskClass, availableLevels, laneType) {
645
+ const baseIndex = REASONING_LADDER_V4.indexOf(TASK_CLASS_THINKING_LEVELS_V4[taskClass]);
646
+ const laneStep = laneType ? (LANE_STEP_V4[laneType] ?? 0) : 0;
647
+ const targetIndex = clampToLadderIndexV4(baseIndex + laneStep);
648
+ return clampToAvailableV4(targetIndex, availableLevels);
649
+ }
650
+ /**
651
+ * Uncertainty-aware resolver (spec 008 Req 2 / plan.md step 5).
652
+ *
653
+ * Pipeline: base rule table for `verdict.taskClass` -> lane step -> bounded
654
+ * bias [-2,+2] -> optional hint fusion (±2 bounded, same as v2/v3) -> bounded
655
+ * non-negative confidence escalation (+1 ladder step exactly when
656
+ * `verdict.confidenceBand === "low"` or `verdict.fallbackReason !== null`) ->
657
+ * clamp to `availableLevels`.
658
+ *
659
+ * The confidence-escalation term is strictly `>= 0`: low confidence can only
660
+ * hold the base+lane+bias+hint target or push it one step higher, never lower
661
+ * it. This guarantees a low-confidence verdict never resolves BELOW what
662
+ * `resolveThinkingLevelV4ForAuto` would give the same class (with bias=0,
663
+ * hint=null) — text alone cannot talk the resolver down.
664
+ */
665
+ export function resolveThinkingLevelV4WithUncertainty(verdict, availableLevels, laneType, bias = 0, hint = null) {
666
+ const baseIndex = REASONING_LADDER_V4.indexOf(TASK_CLASS_THINKING_LEVELS_V4[verdict.taskClass]);
667
+ const laneStep = laneType ? (LANE_STEP_V4[laneType] ?? 0) : 0;
668
+ const biasClamped = Math.max(-BIAS_MAX_V4, Math.min(BIAS_MAX_V4, bias));
669
+ let targetIndex = clampToLadderIndexV4(baseIndex + laneStep + biasClamped);
670
+ if (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD_V4) {
671
+ const hintIndex = REASONING_LADDER_V4.indexOf(hint.level);
672
+ if (hintIndex >= 0) {
673
+ const delta = hintIndex - targetIndex;
674
+ const step = Math.max(-BIAS_MAX_V4, Math.min(BIAS_MAX_V4, delta));
675
+ targetIndex = clampToLadderIndexV4(targetIndex + step);
676
+ }
677
+ }
678
+ if (verdict.confidenceBand === "low" || verdict.fallbackReason !== null) {
679
+ targetIndex = clampToLadderIndexV4(targetIndex + LOW_CONFIDENCE_ESCALATION_STEPS_V4);
680
+ }
681
+ return clampToAvailableV4(targetIndex, availableLevels);
682
+ }
683
+ //# sourceMappingURL=reasoning-router-v4.js.map