@yuaone/core 0.9.42 → 1.0.0

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 (154) hide show
  1. package/dist/agent-affordance.d.ts +37 -0
  2. package/dist/agent-affordance.d.ts.map +1 -0
  3. package/dist/agent-affordance.js +139 -0
  4. package/dist/agent-affordance.js.map +1 -0
  5. package/dist/agent-decision-types.d.ts +262 -0
  6. package/dist/agent-decision-types.d.ts.map +1 -0
  7. package/dist/agent-decision-types.js +19 -0
  8. package/dist/agent-decision-types.js.map +1 -0
  9. package/dist/agent-decision.d.ts +52 -0
  10. package/dist/agent-decision.d.ts.map +1 -0
  11. package/dist/agent-decision.js +767 -0
  12. package/dist/agent-decision.js.map +1 -0
  13. package/dist/agent-loop.d.ts +37 -79
  14. package/dist/agent-loop.d.ts.map +1 -1
  15. package/dist/agent-loop.js +730 -586
  16. package/dist/agent-loop.js.map +1 -1
  17. package/dist/agent-reasoning-engine.d.ts +48 -0
  18. package/dist/agent-reasoning-engine.d.ts.map +1 -0
  19. package/dist/agent-reasoning-engine.js +544 -0
  20. package/dist/agent-reasoning-engine.js.map +1 -0
  21. package/dist/codebase-context.d.ts +3 -0
  22. package/dist/codebase-context.d.ts.map +1 -1
  23. package/dist/codebase-context.js +15 -6
  24. package/dist/codebase-context.js.map +1 -1
  25. package/dist/command-plan-compiler.d.ts +43 -0
  26. package/dist/command-plan-compiler.d.ts.map +1 -0
  27. package/dist/command-plan-compiler.js +164 -0
  28. package/dist/command-plan-compiler.js.map +1 -0
  29. package/dist/dependency-guard.d.ts +18 -0
  30. package/dist/dependency-guard.d.ts.map +1 -0
  31. package/dist/dependency-guard.js +113 -0
  32. package/dist/dependency-guard.js.map +1 -0
  33. package/dist/execution-engine.d.ts +10 -1
  34. package/dist/execution-engine.d.ts.map +1 -1
  35. package/dist/execution-engine.js +162 -8
  36. package/dist/execution-engine.js.map +1 -1
  37. package/dist/execution-receipt.d.ts +62 -0
  38. package/dist/execution-receipt.d.ts.map +1 -0
  39. package/dist/execution-receipt.js +67 -0
  40. package/dist/execution-receipt.js.map +1 -0
  41. package/dist/failure-surface-writer.d.ts +13 -0
  42. package/dist/failure-surface-writer.d.ts.map +1 -0
  43. package/dist/failure-surface-writer.js +33 -0
  44. package/dist/failure-surface-writer.js.map +1 -0
  45. package/dist/file-chunker.d.ts +26 -0
  46. package/dist/file-chunker.d.ts.map +1 -0
  47. package/dist/file-chunker.js +103 -0
  48. package/dist/file-chunker.js.map +1 -0
  49. package/dist/image-observer.d.ts +22 -0
  50. package/dist/image-observer.d.ts.map +1 -0
  51. package/dist/image-observer.js +60 -0
  52. package/dist/image-observer.js.map +1 -0
  53. package/dist/index.d.ts +55 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +53 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/judgment-rules.d.ts +44 -0
  58. package/dist/judgment-rules.d.ts.map +1 -0
  59. package/dist/judgment-rules.js +185 -0
  60. package/dist/judgment-rules.js.map +1 -0
  61. package/dist/memory-decay.d.ts +41 -0
  62. package/dist/memory-decay.d.ts.map +1 -0
  63. package/dist/memory-decay.js +62 -0
  64. package/dist/memory-decay.js.map +1 -0
  65. package/dist/memory-manager.d.ts.map +1 -1
  66. package/dist/memory-manager.js +30 -0
  67. package/dist/memory-manager.js.map +1 -1
  68. package/dist/model-weakness-tracker.d.ts +42 -0
  69. package/dist/model-weakness-tracker.d.ts.map +1 -0
  70. package/dist/model-weakness-tracker.js +107 -0
  71. package/dist/model-weakness-tracker.js.map +1 -0
  72. package/dist/overhead-governor.d.ts +3 -1
  73. package/dist/overhead-governor.d.ts.map +1 -1
  74. package/dist/overhead-governor.js +5 -0
  75. package/dist/overhead-governor.js.map +1 -1
  76. package/dist/patch-scope-controller.d.ts +44 -0
  77. package/dist/patch-scope-controller.d.ts.map +1 -0
  78. package/dist/patch-scope-controller.js +107 -0
  79. package/dist/patch-scope-controller.js.map +1 -0
  80. package/dist/patch-transaction.d.ts +53 -0
  81. package/dist/patch-transaction.d.ts.map +1 -0
  82. package/dist/patch-transaction.js +119 -0
  83. package/dist/patch-transaction.js.map +1 -0
  84. package/dist/pre-write-validator.d.ts +29 -0
  85. package/dist/pre-write-validator.d.ts.map +1 -0
  86. package/dist/pre-write-validator.js +97 -0
  87. package/dist/pre-write-validator.js.map +1 -0
  88. package/dist/prompt-builder.d.ts +25 -0
  89. package/dist/prompt-builder.d.ts.map +1 -0
  90. package/dist/prompt-builder.js +93 -0
  91. package/dist/prompt-builder.js.map +1 -0
  92. package/dist/prompt-envelope.d.ts +40 -0
  93. package/dist/prompt-envelope.d.ts.map +1 -0
  94. package/dist/prompt-envelope.js +16 -0
  95. package/dist/prompt-envelope.js.map +1 -0
  96. package/dist/prompt-runtime.d.ts +66 -0
  97. package/dist/prompt-runtime.d.ts.map +1 -0
  98. package/dist/prompt-runtime.js +492 -0
  99. package/dist/prompt-runtime.js.map +1 -0
  100. package/dist/repo-capability-profile.d.ts +24 -0
  101. package/dist/repo-capability-profile.d.ts.map +1 -0
  102. package/dist/repo-capability-profile.js +113 -0
  103. package/dist/repo-capability-profile.js.map +1 -0
  104. package/dist/security-gate.d.ts +39 -0
  105. package/dist/security-gate.d.ts.map +1 -0
  106. package/dist/security-gate.js +121 -0
  107. package/dist/security-gate.js.map +1 -0
  108. package/dist/self-evaluation.d.ts +22 -0
  109. package/dist/self-evaluation.d.ts.map +1 -0
  110. package/dist/self-evaluation.js +43 -0
  111. package/dist/self-evaluation.js.map +1 -0
  112. package/dist/semantic-diff-reviewer.d.ts +28 -0
  113. package/dist/semantic-diff-reviewer.d.ts.map +1 -0
  114. package/dist/semantic-diff-reviewer.js +168 -0
  115. package/dist/semantic-diff-reviewer.js.map +1 -0
  116. package/dist/stall-detector.d.ts +26 -2
  117. package/dist/stall-detector.d.ts.map +1 -1
  118. package/dist/stall-detector.js +128 -3
  119. package/dist/stall-detector.js.map +1 -1
  120. package/dist/system-core.d.ts +27 -0
  121. package/dist/system-core.d.ts.map +1 -0
  122. package/dist/system-core.js +269 -0
  123. package/dist/system-core.js.map +1 -0
  124. package/dist/system-prompt.d.ts +4 -0
  125. package/dist/system-prompt.d.ts.map +1 -1
  126. package/dist/system-prompt.js +47 -218
  127. package/dist/system-prompt.js.map +1 -1
  128. package/dist/target-file-ranker.d.ts +38 -0
  129. package/dist/target-file-ranker.d.ts.map +1 -0
  130. package/dist/target-file-ranker.js +90 -0
  131. package/dist/target-file-ranker.js.map +1 -0
  132. package/dist/task-classifier.d.ts +6 -0
  133. package/dist/task-classifier.d.ts.map +1 -1
  134. package/dist/task-classifier.js +6 -0
  135. package/dist/task-classifier.js.map +1 -1
  136. package/dist/test-impact-planner.d.ts +16 -0
  137. package/dist/test-impact-planner.d.ts.map +1 -0
  138. package/dist/test-impact-planner.js +68 -0
  139. package/dist/test-impact-planner.js.map +1 -0
  140. package/dist/tool-outcome-cache.d.ts +41 -0
  141. package/dist/tool-outcome-cache.d.ts.map +1 -0
  142. package/dist/tool-outcome-cache.js +88 -0
  143. package/dist/tool-outcome-cache.js.map +1 -0
  144. package/dist/types.d.ts +39 -0
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/verifier-rules.d.ts +15 -0
  147. package/dist/verifier-rules.d.ts.map +1 -0
  148. package/dist/verifier-rules.js +80 -0
  149. package/dist/verifier-rules.js.map +1 -0
  150. package/dist/workspace-mutation-policy.d.ts +28 -0
  151. package/dist/workspace-mutation-policy.d.ts.map +1 -0
  152. package/dist/workspace-mutation-policy.js +56 -0
  153. package/dist/workspace-mutation-policy.js.map +1 -0
  154. package/package.json +1 -1
@@ -0,0 +1,767 @@
1
+ /**
2
+ * @module agent-decision
3
+ * @description Main Agent Decision Orchestrator.
4
+ * Single entry point: `agentDecide()` — called once per user message.
5
+ * Produces an immutable, deep-frozen `AgentDecisionContext` (SSOT).
6
+ *
7
+ * Pure function (except `crypto.randomUUID()` for session ID in meta).
8
+ * NO LLM calls, NO async.
9
+ *
10
+ * Design spec: docs/superpowers/specs/2026-03-17-yuan-agent-decision-engine-design.md, section 6
11
+ */
12
+ import { randomUUID } from "node:crypto";
13
+ import { agentReason } from "./agent-reasoning-engine.js";
14
+ import { computeAgentAffordance, applyStuckBreaker } from "./agent-affordance.js";
15
+ // ─── Utilities ───
16
+ /** Clamp a number to [0, 1]. */
17
+ function clamp01(x) {
18
+ return Math.max(0, Math.min(1, x));
19
+ }
20
+ /**
21
+ * Deep freeze utility — recursively freezes nested objects/arrays (GPT QA #1).
22
+ * Returns the same object reference, frozen in place.
23
+ */
24
+ function deepFreeze(obj) {
25
+ Object.freeze(obj);
26
+ for (const val of Object.values(obj)) {
27
+ if (val && typeof val === "object" && !Object.isFrozen(val)) {
28
+ deepFreeze(val);
29
+ }
30
+ }
31
+ return obj;
32
+ }
33
+ // ─── ComputePolicy (§6.2) ───
34
+ const POLICY_TABLE = {
35
+ trivial: { maxIterations: 3, maxTokenBudget: 8_000, modelTier: "fast", parallelAgents: 0 },
36
+ simple: { maxIterations: 8, maxTokenBudget: 20_000, modelTier: "fast", parallelAgents: 0 },
37
+ moderate: { maxIterations: 15, maxTokenBudget: 50_000, modelTier: "standard", parallelAgents: 1 },
38
+ complex: { maxIterations: 30, maxTokenBudget: 100_000, modelTier: "standard", parallelAgents: 2 },
39
+ massive: { maxIterations: 50, maxTokenBudget: 200_000, modelTier: "deep", parallelAgents: 3 },
40
+ };
41
+ function deriveComputePolicy(reasoning) {
42
+ const { complexity, depthHint } = reasoning;
43
+ const base = POLICY_TABLE[complexity];
44
+ // Deep hint override: promote fast → standard when deep analysis requested
45
+ if (depthHint === "deep" && base.modelTier === "fast") {
46
+ return { ...base, modelTier: "standard" };
47
+ }
48
+ return base;
49
+ }
50
+ // ─── FailureSurface (§6.4) ───
51
+ /**
52
+ * Risk balance formula from YUA pipeline-lite: stability/risk-balance.ts
53
+ * Adjusts risk threshold based on FP/FN cost asymmetry.
54
+ * For coding: FP cost (unnecessary caution) = 0.3, FN cost (missed bug) = 0.7
55
+ */
56
+ function computeRiskBalance(fpCost, fnCost, tau) {
57
+ return (tau + fpCost) / fnCost;
58
+ }
59
+ const RISK_BALANCE = computeRiskBalance(0.3, 0.7, 0.5); // ~1.14 — slightly risk-averse
60
+ const COMPLEXITY_RISK = {
61
+ trivial: 0, simple: 0.1, moderate: 0.2, complex: 0.3, massive: 0.5,
62
+ };
63
+ const BLAST_RADIUS = {
64
+ trivial: 0.05, simple: 0.1, moderate: 0.25, complex: 0.5, massive: 0.8,
65
+ };
66
+ function estimateFailureSurface(reasoning, projectContext) {
67
+ const { intent, complexity, taskStage, confidence } = reasoning;
68
+ // Patch risk: edit/refactor/fix intents carry higher base risk
69
+ // Risk balance (YUA FSLE): FP/FN cost-adjusted risk multiplier
70
+ const isModifyIntent = intent === "edit" || intent === "refactor" || intent === "fix";
71
+ let patchRisk = clamp01(((isModifyIntent ? 0.3 : 0.1) + COMPLEXITY_RISK[complexity]) * RISK_BALANCE);
72
+ // High codebase complexity → patch risk bump
73
+ if (projectContext?.avgCyclomaticComplexity && projectContext.avgCyclomaticComplexity > 15) {
74
+ patchRisk = clamp01(patchRisk + 0.1);
75
+ }
76
+ if (projectContext?.maxFileComplexity && projectContext.maxFileComplexity > 50) {
77
+ patchRisk = clamp01(patchRisk + 0.1);
78
+ }
79
+ // Build risk: higher for compiled languages + existing build errors
80
+ const isCompiled = projectContext?.language !== "python" && projectContext?.language !== "shell";
81
+ let buildRisk = clamp01(patchRisk * (isCompiled ? 1.2 : 0.5));
82
+ if (projectContext?.hasBuildErrors)
83
+ buildRisk = clamp01(buildRisk + 0.2);
84
+ // Test risk: higher when project has no tests or has recent failures
85
+ let testRisk = clamp01(patchRisk * (projectContext?.hasTests ? 0.6 : 1.3));
86
+ if (projectContext?.recentFailureCount && projectContext.recentFailureCount > 0) {
87
+ testRisk = clamp01(testRisk + 0.15);
88
+ }
89
+ // Blast radius: complexity-based + dirty tree bump
90
+ let blastRadius = clamp01(BLAST_RADIUS[complexity]);
91
+ if (projectContext?.dirtyWorkingTree)
92
+ blastRadius = clamp01(blastRadius + 0.1);
93
+ // Ambiguity risk: underspecified + low confidence + missing deps + conflicts
94
+ let ambiguityRisk = clamp01((taskStage === "underspecified" ? 0.5 : 0.1) + (1 - confidence) * 0.3);
95
+ if (projectContext?.missingDeps)
96
+ ambiguityRisk = clamp01(ambiguityRisk + 0.15);
97
+ if (projectContext?.hasConflicts)
98
+ ambiguityRisk = clamp01(ambiguityRisk + 0.2);
99
+ return { patchRisk, buildRisk, testRisk, blastRadius, ambiguityRisk };
100
+ }
101
+ // ─── VetoFlags (GPT QA #6) ───
102
+ function computeVetoFlags(surface, planRequired) {
103
+ return {
104
+ editVetoed: surface.patchRisk >= 0.8 && !planRequired,
105
+ verifyRequired: surface.blastRadius >= 0.6,
106
+ clarifyForced: surface.ambiguityRisk >= 0.7,
107
+ finalizeBlocked: surface.buildRisk >= 0.7,
108
+ };
109
+ }
110
+ const BASE_BUDGET = {
111
+ trivial: { maxFileReads: 3, maxEdits: 2, maxShellExecs: 1, maxTestRuns: 1, maxSearches: 3, maxWebLookups: 0, shellCostUnits: 1, maxSameFileEdits: 2 },
112
+ simple: { maxFileReads: 8, maxEdits: 4, maxShellExecs: 3, maxTestRuns: 2, maxSearches: 8, maxWebLookups: 1, shellCostUnits: 2, maxSameFileEdits: 3 },
113
+ moderate: { maxFileReads: 15, maxEdits: 8, maxShellExecs: 5, maxTestRuns: 3, maxSearches: 15, maxWebLookups: 2, shellCostUnits: 3, maxSameFileEdits: 4 },
114
+ complex: { maxFileReads: 30, maxEdits: 15, maxShellExecs: 10, maxTestRuns: 5, maxSearches: 30, maxWebLookups: 3, shellCostUnits: 5, maxSameFileEdits: 5 },
115
+ massive: { maxFileReads: 60, maxEdits: 30, maxShellExecs: 20, maxTestRuns: 8, maxSearches: 60, maxWebLookups: 5, shellCostUnits: 8, maxSameFileEdits: 6 },
116
+ };
117
+ function deriveToolBudget(reasoning, affordance) {
118
+ const { complexity } = reasoning;
119
+ const budget = { ...BASE_BUDGET[complexity] };
120
+ // Affordance-based adjustments
121
+ if (affordance.inspect_more > 0.7) {
122
+ budget.maxFileReads = Math.ceil(budget.maxFileReads * 1.5);
123
+ budget.maxSearches = Math.ceil(budget.maxSearches * 1.5);
124
+ }
125
+ if (affordance.run_checks > 0.7) {
126
+ budget.maxTestRuns = Math.ceil(budget.maxTestRuns * 1.5);
127
+ budget.maxShellExecs = Math.ceil(budget.maxShellExecs * 1.3);
128
+ }
129
+ if (affordance.edit_now < 0.3) {
130
+ budget.maxEdits = Math.ceil(budget.maxEdits * 0.5);
131
+ }
132
+ return budget;
133
+ }
134
+ // ─── MemoryIntent (§13.3) ───
135
+ function deriveMemoryIntent(reasoning) {
136
+ const { intent, complexity } = reasoning;
137
+ // Simple lookups don't need memory saves
138
+ if (intent === "inspect" || intent === "read" || intent === "search") {
139
+ return { shouldSave: false, categories: [], priority: "low", target: "local" };
140
+ }
141
+ const categories = [];
142
+ // Modify intents → tool_pattern + file_structure
143
+ if (intent === "edit" || intent === "refactor" || intent === "fix") {
144
+ categories.push("tool_pattern", "file_structure");
145
+ }
146
+ // Fix → error_pattern + failure_avoidance
147
+ if (intent === "fix") {
148
+ categories.push("error_pattern", "failure_avoidance");
149
+ }
150
+ // Test/verify → project_rule
151
+ if (intent === "test" || intent === "verify") {
152
+ categories.push("project_rule");
153
+ }
154
+ const priority = complexity === "massive" || complexity === "complex" ? "high" : "normal";
155
+ return {
156
+ shouldSave: categories.length > 0,
157
+ categories,
158
+ priority,
159
+ target: "local",
160
+ };
161
+ }
162
+ // ─── Task Continuation (§14.3) ───
163
+ function detectTaskContinuation(message, prevDecision) {
164
+ if (!prevDecision) {
165
+ return {
166
+ isContinuation: false,
167
+ continuityScore: 0,
168
+ carryover: { modifiedFiles: [], failedAttempts: [] },
169
+ };
170
+ }
171
+ let score = 0;
172
+ // Reference to previous context
173
+ if (/(아까|이전|위에|방금|earlier|previous|that file|same)/i.test(message)) {
174
+ score += 0.3;
175
+ }
176
+ // Short continuation commands
177
+ if (/^(계속|다음|진행|ㄱㄱ|go|continue|next|이어서)$/i.test(message.trim())) {
178
+ score += 0.5;
179
+ }
180
+ // Retry/correction requests
181
+ if (/(다시|다르게|수정|고쳐|retry|again|differently)/i.test(message)) {
182
+ score += 0.4;
183
+ }
184
+ // Iterating stage from previous decision
185
+ if (prevDecision.core.reasoning.taskStage === "iterating") {
186
+ score += 0.2;
187
+ }
188
+ const isContinuation = score >= 0.4;
189
+ return {
190
+ isContinuation,
191
+ continuityScore: clamp01(score),
192
+ carryover: {
193
+ modifiedFiles: [],
194
+ failedAttempts: [],
195
+ prevIntent: prevDecision.core.reasoning.intent,
196
+ prevStage: prevDecision.core.reasoning.taskStage,
197
+ },
198
+ };
199
+ }
200
+ // ─── MicroPlan (GPT QA #17) ───
201
+ function deriveMicroPlan(anchors) {
202
+ return anchors.map((anchor) => {
203
+ switch (anchor) {
204
+ case "SEARCH_REPO": return "Search the codebase for relevant files";
205
+ case "READ_FILES": return "Read and understand the target files";
206
+ case "PREPARE_PATCH": return "Prepare the code changes";
207
+ case "RUN_TESTS": return "Run tests to verify changes";
208
+ case "VERIFY_RESULT": return "Verify build and type-check pass";
209
+ case "SUMMARIZE_CHANGE": return "Summarize what was changed";
210
+ }
211
+ });
212
+ }
213
+ // ─── InteractionMode (Dual-Mode) ───
214
+ /**
215
+ * Derive the interaction mode from reasoning result.
216
+ * Deterministic: same reasoning always yields the same mode.
217
+ *
218
+ * Rules:
219
+ * - CHAT: trivial + explore intents (inspect/read/search), OR underspecified
220
+ * - AGENT: complex/massive, OR plan/refactor intent, OR fix+moderate+
221
+ * - HYBRID: moderate edit/fix/test/verify, OR simple edit/fix
222
+ * - Default: CHAT
223
+ */
224
+ export function deriveInteractionMode(reasoning) {
225
+ const { intent, complexity, taskStage } = reasoning;
226
+ // CHAT: trivial complexity with explore-type intents
227
+ if (complexity === "trivial" && (intent === "inspect" || intent === "read" || intent === "search")) {
228
+ return "CHAT";
229
+ }
230
+ // CHAT: underspecified → clarification-only, no execution
231
+ if (taskStage === "underspecified") {
232
+ return "CHAT";
233
+ }
234
+ // AGENT: complex/massive complexity
235
+ if (complexity === "complex" || complexity === "massive") {
236
+ return "AGENT";
237
+ }
238
+ // AGENT: plan/refactor intents always need full agent
239
+ if (intent === "plan" || intent === "refactor") {
240
+ return "AGENT";
241
+ }
242
+ // AGENT: fix intent at moderate+ complexity
243
+ if (intent === "fix" && complexity !== "simple") {
244
+ return "AGENT";
245
+ }
246
+ // HYBRID: moderate complexity with any intent
247
+ if (complexity === "moderate") {
248
+ return "HYBRID";
249
+ }
250
+ // HYBRID: simple edit/fix/test/verify (verify is still useful)
251
+ if (intent === "edit" || intent === "fix" || intent === "test" || intent === "verify") {
252
+ return "HYBRID";
253
+ }
254
+ // Default: CHAT
255
+ return "CHAT";
256
+ }
257
+ // ─── SubAgent Plan (Phase I SSOT) ───
258
+ function deriveSubAgentPlan(reasoning) {
259
+ const { intent, complexity } = reasoning;
260
+ // Only enable for complex+ tasks
261
+ if (complexity === "trivial" || complexity === "simple" || complexity === "moderate") {
262
+ return { enabled: false, maxAgents: 0, roles: [], strategy: "sequential" };
263
+ }
264
+ // complex/massive: intent-based role selection
265
+ const roleMap = {
266
+ fix: ["debugger", "coder", "tester"],
267
+ refactor: ["planner", "refactorer", "tester"],
268
+ edit: ["coder", "reviewer"],
269
+ test: ["tester", "coder"],
270
+ plan: ["planner"],
271
+ verify: ["tester", "reviewer"],
272
+ };
273
+ const roles = roleMap[intent] ?? ["coder"];
274
+ const maxAgents = complexity === "massive" ? 3 : 2;
275
+ const strategy = complexity === "massive" ? "parallel" : "sequential";
276
+ return { enabled: true, maxAgents, roles, strategy };
277
+ }
278
+ // ─── Persona Hint (Phase I+ SSOT) ───
279
+ function derivePersonaHint(message) {
280
+ // Detect language
281
+ const hasKorean = /[가-힣]/.test(message);
282
+ const language = hasKorean ? "ko" : "auto";
283
+ // Detect tone from message patterns
284
+ const isCasual = /ㄱㄱ|ㅋㅋ|ㅎㅎ|ㅇㅇ|해줘|고쳐|봐봐|알려줘/i.test(message);
285
+ const isTechnical = /architecture|설계|구조|리팩토링|타입|인터페이스/i.test(message);
286
+ const tone = isCasual ? "casual" : isTechnical ? "technical" : "professional";
287
+ return { tone, language };
288
+ }
289
+ // ─── Memory Load (Phase I+ SSOT) ───
290
+ function deriveMemoryLoad(reasoning) {
291
+ // Trivial tasks don't need memory
292
+ if (reasoning.complexity === "trivial") {
293
+ return { shouldLoad: false, categories: [] };
294
+ }
295
+ // Exploration: load file_structure + project_rule
296
+ if (reasoning.intent === "inspect" || reasoning.intent === "read" || reasoning.intent === "search") {
297
+ return { shouldLoad: true, categories: ["file_structure", "project_rule"] };
298
+ }
299
+ // Modify: load everything relevant
300
+ if (reasoning.intent === "edit" || reasoning.intent === "refactor" || reasoning.intent === "fix") {
301
+ return { shouldLoad: true, categories: ["tool_pattern", "error_pattern", "project_rule", "file_structure"] };
302
+ }
303
+ // Default: load basics
304
+ return { shouldLoad: true, categories: ["project_rule"] };
305
+ }
306
+ // ─── Skill Activation (Phase I SSOT) ───
307
+ function deriveSkillActivation(reasoning) {
308
+ const { intent, complexity } = reasoning;
309
+ const isModify = intent === "edit" || intent === "refactor" || intent === "fix";
310
+ return {
311
+ enableToolPlanning: isModify && complexity !== "trivial",
312
+ enableSkillLearning: complexity !== "trivial",
313
+ enablePlugins: true, // always on, runtime matching
314
+ enableSpecialist: isModify && complexity !== "trivial" && complexity !== "simple",
315
+ specialistDomain: undefined, // could infer from reasoning, MVP = undefined
316
+ };
317
+ }
318
+ // ─── Code Quality Policy ───
319
+ function deriveCodeQualityPolicy(reasoning) {
320
+ const { intent, complexity } = reasoning;
321
+ // Not a code task
322
+ if (intent === "inspect" || intent === "read" || intent === "search") {
323
+ return { isCodeTask: false, codeTaskType: "none", primaryRisk: "none", constraints: [], strictMode: false, preEditVerify: false };
324
+ }
325
+ // Map intent → code task type
326
+ const taskTypeMap = {
327
+ edit: "generation",
328
+ fix: "fix",
329
+ refactor: "refactor",
330
+ test: "test",
331
+ verify: "review",
332
+ plan: "none",
333
+ };
334
+ const codeTaskType = taskTypeMap[intent] ?? "none";
335
+ // Map task type → primary risk
336
+ const riskMap = {
337
+ generation: "extension_pain",
338
+ fix: "async_race",
339
+ refactor: "state_corruption",
340
+ review: "state_corruption",
341
+ test: "type_safety",
342
+ none: "none",
343
+ };
344
+ const primaryRisk = riskMap[codeTaskType] ?? "none";
345
+ // Constraints based on task type
346
+ const constraints = [];
347
+ if (codeTaskType === "generation") {
348
+ constraints.push("Write complete, production-ready code. No TODO, FIXME, placeholder, stub, or empty implementations.", "If requirements are unclear, state assumptions explicitly then implement fully based on those assumptions.", "Do NOT ask clarifying questions mid-implementation. Make reasonable assumptions and proceed.", "Each file must be a complete, working module. No partial implementations.");
349
+ }
350
+ if (codeTaskType === "fix") {
351
+ constraints.push("Reproduce the issue mentally first. Identify root cause before writing any fix.", "Apply the minimal correct fix. Do not refactor unrelated code.", "Verify the fix does not introduce new issues (type-check, edge cases).");
352
+ }
353
+ if (codeTaskType === "refactor") {
354
+ constraints.push("Do NOT change behavior. Only restructure.", "Verify all existing tests still pass after refactoring.", "Prefer smaller, incremental refactors over large rewrites.");
355
+ }
356
+ if (codeTaskType === "test") {
357
+ constraints.push("Test behavior, not implementation details.", "Cover edge cases and failure paths.", "Tests must be deterministic — no random data, no timing dependencies.");
358
+ }
359
+ if (codeTaskType === "review") {
360
+ constraints.push("Do NOT modify code. Only analyze and report.", "Cite specific file and line numbers for each issue found.", "Prioritize: correctness → security → performance → readability.");
361
+ }
362
+ // Complexity-based additions
363
+ if (complexity === "complex" || complexity === "massive") {
364
+ constraints.push("Break the implementation into clearly separated modules/functions.", "Add JSDoc comments for public APIs only (not for internal helpers).");
365
+ }
366
+ // Risk-based additions
367
+ if (primaryRisk === "extension_pain") {
368
+ constraints.push("Design for extension: use interfaces/types at boundaries. Future requirements should not require rewriting core logic.");
369
+ }
370
+ if (primaryRisk === "type_safety") {
371
+ constraints.push("Never use 'any'. Use proper TypeScript types. Validate at system boundaries.");
372
+ }
373
+ if (primaryRisk === "async_race") {
374
+ constraints.push("Check for race conditions in async code. Ensure proper error handling in Promise chains.");
375
+ }
376
+ if (primaryRisk === "state_corruption") {
377
+ constraints.push("Verify state mutations are atomic and predictable. No hidden side effects.");
378
+ }
379
+ const strictMode = codeTaskType !== "none" && codeTaskType !== "review";
380
+ const preEditVerify = complexity !== "trivial" && complexity !== "simple" && codeTaskType !== "none";
381
+ return { isCodeTask: codeTaskType !== "none", codeTaskType, primaryRisk, constraints, strictMode, preEditVerify };
382
+ }
383
+ // ─── LeadHint ───
384
+ function deriveLeadHint(reasoning, mode) {
385
+ if (mode === "CHAT")
386
+ return "NONE";
387
+ if (mode === "AGENT")
388
+ return "HARD";
389
+ // HYBRID: depends on intent
390
+ if (reasoning.intent === "plan" || reasoning.intent === "refactor")
391
+ return "HARD";
392
+ if (reasoning.intent === "fix" || reasoning.intent === "edit")
393
+ return "SOFT";
394
+ return "NONE";
395
+ }
396
+ // ─── ResponseHint ───
397
+ function deriveResponseHint(reasoning, mode) {
398
+ const { intent, complexity } = reasoning;
399
+ // Structure based on intent
400
+ const structureMap = {
401
+ inspect: "direct_answer",
402
+ read: "direct_answer",
403
+ search: "direct_answer",
404
+ fix: "problem_solution",
405
+ plan: "stepwise_explanation",
406
+ refactor: "stepwise_explanation",
407
+ edit: "code_first",
408
+ test: "code_first",
409
+ verify: "direct_answer",
410
+ };
411
+ // Expansion based on mode + complexity
412
+ const expansion = mode === "CHAT" ? "soft"
413
+ : complexity === "trivial" ? "none"
414
+ : complexity === "massive" ? "full"
415
+ : "guided";
416
+ return {
417
+ structure: structureMap[intent] ?? "direct_answer",
418
+ expansion,
419
+ forbid: {
420
+ metaComment: mode !== "CHAT",
421
+ narration: false, // coding agent needs narration for tool calls
422
+ apology: true, // never apologize
423
+ },
424
+ };
425
+ }
426
+ // ─── ToolGate ───
427
+ function deriveToolGate(reasoning, vetoFlags) {
428
+ const { intent, taskStage } = reasoning;
429
+ // Read-only for inspect/read/search/review
430
+ if (["inspect", "read", "search"].includes(intent) || vetoFlags.editVetoed) {
431
+ return { level: "READ_ONLY", blockedTools: ["file_write", "file_edit", "git_ops"], verifierBudget: 0 };
432
+ }
433
+ // Limited for underspecified
434
+ if (taskStage === "underspecified") {
435
+ return { level: "LIMITED", blockedTools: ["file_write", "git_ops"], verifierBudget: 3 };
436
+ }
437
+ // Full for everything else
438
+ return { level: "FULL", blockedTools: [], verifierBudget: 10 };
439
+ }
440
+ // ─── ResponsePressure ───
441
+ function deriveResponsePressure(reasoning, affordance, mode, codeQuality) {
442
+ // Code generation → always assertive
443
+ if (codeQuality.isCodeTask && codeQuality.strictMode) {
444
+ return { pressure: "ASSERTIVE", momentum: "HIGH" };
445
+ }
446
+ // CHAT mode → gentle
447
+ if (mode === "CHAT") {
448
+ return { pressure: "GENTLE", momentum: "LOW" };
449
+ }
450
+ // Underspecified → gentle
451
+ if (reasoning.taskStage === "underspecified") {
452
+ return { pressure: "GENTLE", momentum: "LOW" };
453
+ }
454
+ // Compute momentum from affordance
455
+ const momentumScore = affordance.edit_now * 0.5 + affordance.run_checks * 0.3 + (1 - affordance.finalize) * 0.2;
456
+ const momentum = momentumScore > 0.65 ? "HIGH" : momentumScore > 0.35 ? "MEDIUM" : "LOW";
457
+ const pressure = momentum === "HIGH" ? "ASSERTIVE" : "NEUTRAL";
458
+ return { pressure, momentum };
459
+ }
460
+ // ─── ContinuityCapsule ───
461
+ function deriveContinuityCapsule(continuation) {
462
+ if (!continuation.isContinuation) {
463
+ return { enabled: false, rules: [] };
464
+ }
465
+ const rules = [
466
+ "This is a continuation of a previous task. Do not start over.",
467
+ "Do not ask 'what would you like me to do?' — the task is already defined.",
468
+ ];
469
+ if (continuation.carryover.prevIntent) {
470
+ rules.push(`Previous intent was: ${continuation.carryover.prevIntent}. Continue in that direction.`);
471
+ }
472
+ if (continuation.carryover.modifiedFiles.length > 0) {
473
+ rules.push(`Previously modified files: ${continuation.carryover.modifiedFiles.join(", ")}. Build on these changes.`);
474
+ }
475
+ return { enabled: true, rules };
476
+ }
477
+ // ─── StyleHint ───
478
+ function deriveStyleHint(message) {
479
+ const hasKorean = /[가-힣]/.test(message);
480
+ const hasEnglish = /[a-zA-Z]{3,}/.test(message);
481
+ const hasSlang = /ㅋㅋ|ㅎㅎ|ㅇㅇ|ㄱㄱ|ㅅㅂ|lol|lmao|haha/i.test(message);
482
+ const isFormal = /please|부탁|감사|요청|확인 부탁/i.test(message);
483
+ const isShort = message.trim().length < 20;
484
+ return {
485
+ formality: hasSlang ? "casual" : isFormal ? "formal" : "neutral",
486
+ language: hasKorean && hasEnglish ? "mixed" : hasKorean ? "ko" : "en",
487
+ brevity: isShort ? "terse" : "normal",
488
+ };
489
+ }
490
+ // ─── Main Orchestrator ───
491
+ /**
492
+ * Single entry point for the Agent Decision Engine.
493
+ * Called once per user message. Produces an immutable `AgentDecisionContext`.
494
+ *
495
+ * Pipeline:
496
+ * agentReason → computeAgentAffordance → deriveComputePolicy →
497
+ * estimateFailureSurface → computeVetoFlags → deriveToolBudget →
498
+ * deriveMemoryIntent → detectTaskContinuation → deriveMicroPlan → deepFreeze
499
+ *
500
+ * Supports recoveryHint (GPT QA #8): if provided, adjusts the reasoning stage.
501
+ */
502
+ export function agentDecide(input) {
503
+ const { message, projectContext, prevDecision, recoveryHint } = input;
504
+ // Step 1: Reasoning (pure heuristic, NO LLM)
505
+ const reasoning = agentReason(message, projectContext);
506
+ // Apply recovery hint stage override if present (GPT QA #18)
507
+ if (recoveryHint?.stageHint) {
508
+ reasoning.taskStage = recoveryHint.stageHint;
509
+ }
510
+ // Step 2: Affordance (pure math) + stuck breaker
511
+ let repeatCount = 0;
512
+ if (prevDecision) {
513
+ const prev = prevDecision.core.reasoning;
514
+ if (prev.intent === reasoning.intent && prev.taskStage === reasoning.taskStage) {
515
+ repeatCount = prevDecision.meta.repeatCount + 1;
516
+ }
517
+ }
518
+ let affordance = computeAgentAffordance(reasoning, prevDecision?.core.affordance);
519
+ if (repeatCount >= 3) {
520
+ affordance = applyStuckBreaker(affordance, repeatCount);
521
+ }
522
+ // Step 3: Compute Policy (complexity → budget table)
523
+ const computePolicy = deriveComputePolicy(reasoning);
524
+ // Step 4: Failure Surface (intent × complexity × project context)
525
+ const failureSurface = estimateFailureSurface(reasoning, projectContext);
526
+ // Step 5: Tool Budget (complexity × affordance)
527
+ const toolBudget = deriveToolBudget(reasoning, affordance);
528
+ // Step 6: Plan required (GPT QA #4 — not just complexity, also intent/stage/surface)
529
+ const planRequired = reasoning.complexity === "complex" || reasoning.complexity === "massive" ||
530
+ reasoning.intent === "plan" || reasoning.intent === "refactor" ||
531
+ reasoning.taskStage === "underspecified" || reasoning.taskStage === "blocked" ||
532
+ failureSurface.blastRadius >= 0.6 ||
533
+ (affordance.run_checks > 0.7 && reasoning.complexity !== "trivial");
534
+ // Update veto with final planRequired
535
+ const vetoFlags = computeVetoFlags(failureSurface, planRequired);
536
+ // Step 8: Next action (GPT QA #5 — clarify path)
537
+ let nextAction = "proceed";
538
+ let clarification;
539
+ if (vetoFlags.clarifyForced || (reasoning.taskStage === "underspecified" && reasoning.confidence < 0.4)) {
540
+ nextAction = "ask_user";
541
+ clarification = {
542
+ reason: `Task is ${reasoning.taskStage} with confidence ${reasoning.confidence}`,
543
+ missingFields: reasoning.taskStage === "underspecified"
544
+ ? ["specific files", "expected behavior"]
545
+ : [],
546
+ suggestedOptions: [],
547
+ allowProceedWithAssumptions: reasoning.confidence > 0.25,
548
+ };
549
+ }
550
+ // Blocked stage → blocked_external (QA #10)
551
+ if (reasoning.taskStage === "blocked" && nextAction === "proceed") {
552
+ nextAction = "blocked_external";
553
+ }
554
+ // Recovery hint may force ask_user
555
+ if (recoveryHint?.action === "ask_user") {
556
+ nextAction = "ask_user";
557
+ if (!clarification) {
558
+ clarification = {
559
+ reason: recoveryHint.reason,
560
+ missingFields: [],
561
+ suggestedOptions: [],
562
+ allowProceedWithAssumptions: false,
563
+ };
564
+ }
565
+ }
566
+ // Step 9: Execution strategy
567
+ const scanBreadth = reasoning.complexity === "massive" ? "wide"
568
+ : reasoning.complexity === "complex" ? "normal"
569
+ : "narrow";
570
+ // Step 10: Verify depth (GPT QA #14 — finalize guard)
571
+ let verifyDepth = affordance.run_checks > 0.6 ? "thorough"
572
+ : affordance.run_checks > 0.3 ? "quick"
573
+ : "skip";
574
+ // verifyRequired veto: minimum quick
575
+ if (vetoFlags.verifyRequired && verifyDepth === "skip") {
576
+ verifyDepth = "quick";
577
+ }
578
+ // Affordance finalize guard (GPT QA #14): verify not run → cap finalize
579
+ if (verifyDepth !== "skip") {
580
+ affordance.finalize = Math.min(affordance.finalize, 0.25);
581
+ }
582
+ if (vetoFlags.finalizeBlocked) {
583
+ affordance.finalize = Math.min(affordance.finalize, 0.1);
584
+ }
585
+ // Step 11: Continuation detection
586
+ const continuation = detectTaskContinuation(message, prevDecision);
587
+ // Continuation adjustments (§14.4)
588
+ if (continuation.isContinuation) {
589
+ affordance.inspect_more = clamp01(affordance.inspect_more * 0.5);
590
+ affordance.edit_now = clamp01(affordance.edit_now * 1.3);
591
+ affordance.explain_plan = clamp01(affordance.explain_plan * 0.3);
592
+ // Previous failures increase patch risk
593
+ if (continuation.carryover.failedAttempts.length > 0) {
594
+ failureSurface.patchRisk = clamp01(failureSurface.patchRisk * 1.3);
595
+ // Recompute veto flags after failureSurface mutation (QA fix #2)
596
+ Object.assign(vetoFlags, computeVetoFlags(failureSurface, planRequired));
597
+ }
598
+ }
599
+ // Step 12: Memory intent
600
+ const memoryIntent = deriveMemoryIntent(reasoning);
601
+ // Step 13: MicroPlan (GPT QA #13 — disabled for ask_user/blocked_external)
602
+ const resolvedAction = nextAction;
603
+ const microPlan = resolvedAction === "proceed"
604
+ ? deriveMicroPlan(reasoning.nextAnchors)
605
+ : undefined;
606
+ // Step 14: InteractionMode (Dual-Mode)
607
+ const interactionMode = deriveInteractionMode(reasoning);
608
+ // Step 15: SubAgent Plan & Skill Activation (Phase I SSOT)
609
+ const subAgentPlan = deriveSubAgentPlan(reasoning);
610
+ const skillActivation = deriveSkillActivation(reasoning);
611
+ // Step 15b: Persona Hint & Memory Load (Phase I+ SSOT)
612
+ const personaHint = derivePersonaHint(message);
613
+ const memoryLoad = deriveMemoryLoad(reasoning);
614
+ // Step 15c: Code Quality Policy (deterministic, no LLM)
615
+ const codeQuality = deriveCodeQualityPolicy(reasoning);
616
+ // Step 15d: LeadHint, ResponseHint, ToolGate, ResponsePressure, ContinuityCapsule, StyleHint
617
+ const leadHint = deriveLeadHint(reasoning, interactionMode);
618
+ const responseHint = deriveResponseHint(reasoning, interactionMode);
619
+ const toolGate = deriveToolGate(reasoning, vetoFlags);
620
+ const pressureDecision = deriveResponsePressure(reasoning, affordance, interactionMode, codeQuality);
621
+ const continuityCapsule = deriveContinuityCapsule(continuation);
622
+ const styleHint = deriveStyleHint(message);
623
+ // Step 16: Assemble core (GPT QA #2 — core vs meta separation)
624
+ const core = {
625
+ reasoning,
626
+ affordance,
627
+ computePolicy,
628
+ failureSurface,
629
+ vetoFlags,
630
+ toolBudget,
631
+ nextAction,
632
+ clarification,
633
+ planRequired,
634
+ scanBreadth,
635
+ verifyDepth,
636
+ memoryIntent,
637
+ continuation,
638
+ microPlan,
639
+ interactionMode,
640
+ subAgentPlan,
641
+ skillActivation,
642
+ personaHint,
643
+ memoryLoad,
644
+ codeQuality,
645
+ leadHint,
646
+ responseHint,
647
+ toolGate,
648
+ pressureDecision,
649
+ continuityCapsule,
650
+ styleHint,
651
+ };
652
+ // Step 17: Deep freeze and return (GPT QA #1)
653
+ return deepFreeze({
654
+ core,
655
+ meta: {
656
+ sessionId: randomUUID(),
657
+ createdAt: Date.now(),
658
+ repeatCount,
659
+ },
660
+ });
661
+ }
662
+ // ─── DEFAULT_DECISION ───
663
+ /**
664
+ * Safe default decision — used when agentDecide() fails or is skipped.
665
+ * Trivial/CHAT/proceed = most conservative, safest behavior.
666
+ * This ensures Decision is NEVER null in the pipeline.
667
+ */
668
+ export const DEFAULT_DECISION = deepFreeze({
669
+ core: {
670
+ reasoning: {
671
+ intent: "inspect",
672
+ taskStage: "ready",
673
+ complexity: "simple",
674
+ confidence: 0.5,
675
+ depthHint: "shallow",
676
+ cognitiveLoad: "low",
677
+ nextAnchors: ["SEARCH_REPO", "READ_FILES"],
678
+ },
679
+ affordance: {
680
+ explain_plan: 0.3,
681
+ inspect_more: 0.5,
682
+ edit_now: 0.3,
683
+ run_checks: 0.3,
684
+ finalize: 0.3,
685
+ },
686
+ computePolicy: {
687
+ maxIterations: 15,
688
+ maxTokenBudget: 50_000,
689
+ modelTier: "standard",
690
+ parallelAgents: 0,
691
+ },
692
+ failureSurface: {
693
+ patchRisk: 0.1,
694
+ buildRisk: 0.1,
695
+ testRisk: 0.1,
696
+ blastRadius: 0.1,
697
+ ambiguityRisk: 0.2,
698
+ },
699
+ vetoFlags: {
700
+ editVetoed: false,
701
+ verifyRequired: false,
702
+ clarifyForced: false,
703
+ finalizeBlocked: false,
704
+ },
705
+ toolBudget: {
706
+ maxFileReads: 8,
707
+ maxEdits: 4,
708
+ maxShellExecs: 3,
709
+ maxTestRuns: 2,
710
+ maxSearches: 8,
711
+ maxWebLookups: 1,
712
+ shellCostUnits: 10,
713
+ maxSameFileEdits: 5,
714
+ },
715
+ nextAction: "proceed",
716
+ clarification: undefined,
717
+ planRequired: false,
718
+ scanBreadth: "narrow",
719
+ verifyDepth: "skip",
720
+ memoryIntent: { shouldSave: false, categories: [], priority: "low", target: "local" },
721
+ continuation: { isContinuation: false, continuityScore: 0, carryover: { modifiedFiles: [], failedAttempts: [] } },
722
+ microPlan: undefined,
723
+ interactionMode: "CHAT",
724
+ subAgentPlan: { enabled: false, maxAgents: 0, roles: [], strategy: "sequential" },
725
+ skillActivation: { enableToolPlanning: false, enableSkillLearning: true, enablePlugins: true, enableSpecialist: false, specialistDomain: undefined },
726
+ personaHint: { tone: "casual", language: "auto" },
727
+ memoryLoad: { shouldLoad: true, categories: ["project_rule"] },
728
+ codeQuality: { isCodeTask: false, codeTaskType: "none", primaryRisk: "none", constraints: [], strictMode: false, preEditVerify: false },
729
+ leadHint: "NONE",
730
+ responseHint: { structure: "direct_answer", expansion: "soft", forbid: { metaComment: false, narration: false, apology: true } },
731
+ toolGate: { level: "FULL", blockedTools: [], verifierBudget: 10 },
732
+ pressureDecision: { pressure: "NEUTRAL", momentum: "LOW" },
733
+ continuityCapsule: { enabled: false, rules: [] },
734
+ styleHint: { formality: "neutral", language: "en", brevity: "normal" },
735
+ },
736
+ meta: {
737
+ sessionId: "default",
738
+ createdAt: 0,
739
+ repeatCount: 0,
740
+ },
741
+ });
742
+ // ─── WorldState → ProjectContext Converter ───
743
+ /**
744
+ * Convert a WorldStateSnapshot into AgentProjectContext for Decision Engine input.
745
+ * This bridges the gap between runtime world state and decision-time project context.
746
+ */
747
+ export function worldStateToProjectContext(ws) {
748
+ return {
749
+ hasTests: ws.test.testRunner !== "unknown",
750
+ fileCount: ws.files.totalFiles,
751
+ language: "typescript",
752
+ repoIndexed: true,
753
+ monorepo: false,
754
+ packageManager: ws.deps.packageManager !== "unknown" ? ws.deps.packageManager : undefined,
755
+ testFrameworkPresent: ws.test.testRunner !== "unknown",
756
+ dirtyWorkingTree: ws.git.uncommittedFiles.length > 0,
757
+ currentBranchProtected: ws.git.branch === "main" || ws.git.branch === "master",
758
+ recentFailureCount: ws.test.failingTests.length + ws.errors.recentRuntimeErrors.length,
759
+ changedFilesCount: ws.files.recentlyChanged.length,
760
+ hasBuildErrors: ws.build.lastResult === "fail",
761
+ hasConflicts: ws.git.hasConflicts,
762
+ missingDeps: ws.deps.missing.length > 0,
763
+ buildTool: ws.build.buildTool !== "unknown" ? ws.build.buildTool : undefined,
764
+ testRunner: ws.test.testRunner !== "unknown" ? ws.test.testRunner : undefined,
765
+ };
766
+ }
767
+ //# sourceMappingURL=agent-decision.js.map