@tienne/gestalt 0.15.0 → 0.15.2

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 (29) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.ko.md +15 -32
  3. package/README.md +183 -163
  4. package/dist/package.json +1 -1
  5. package/dist/src/execute/orchestrators/evaluation.d.ts +27 -0
  6. package/dist/src/execute/orchestrators/evaluation.d.ts.map +1 -0
  7. package/dist/src/execute/orchestrators/evaluation.js +169 -0
  8. package/dist/src/execute/orchestrators/evaluation.js.map +1 -0
  9. package/dist/src/execute/orchestrators/evolution.d.ts +50 -0
  10. package/dist/src/execute/orchestrators/evolution.d.ts.map +1 -0
  11. package/dist/src/execute/orchestrators/evolution.js +391 -0
  12. package/dist/src/execute/orchestrators/evolution.js.map +1 -0
  13. package/dist/src/execute/orchestrators/execution.d.ts +38 -0
  14. package/dist/src/execute/orchestrators/execution.d.ts.map +1 -0
  15. package/dist/src/execute/orchestrators/execution.js +328 -0
  16. package/dist/src/execute/orchestrators/execution.js.map +1 -0
  17. package/dist/src/execute/orchestrators/planning.d.ts +26 -0
  18. package/dist/src/execute/orchestrators/planning.d.ts.map +1 -0
  19. package/dist/src/execute/orchestrators/planning.js +269 -0
  20. package/dist/src/execute/orchestrators/planning.js.map +1 -0
  21. package/dist/src/execute/orchestrators/types.d.ts +137 -0
  22. package/dist/src/execute/orchestrators/types.d.ts.map +1 -0
  23. package/dist/src/execute/orchestrators/types.js +2 -0
  24. package/dist/src/execute/orchestrators/types.js.map +1 -0
  25. package/dist/src/execute/passthrough-engine.d.ts +9 -201
  26. package/dist/src/execute/passthrough-engine.d.ts.map +1 -1
  27. package/dist/src/execute/passthrough-engine.js +37 -1075
  28. package/dist/src/execute/passthrough-engine.js.map +1 -1
  29. package/package.json +1 -1
@@ -1,1125 +1,87 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { ExecuteError, ExecuteSessionNotFoundError, InvalidPlanningStepError, TaskExecutionError, EvaluationError, } from '../core/errors.js';
3
- import { ok, err } from '../core/result.js';
4
- import { PLANNING_PRINCIPLE_SEQUENCE, PLANNING_TOTAL_STEPS, PLANNING_PRINCIPLE_STRATEGIES, MAX_ATOMIC_TASKS, MAX_TASK_GROUPS, } from '../core/constants.js';
5
- import { EventType } from '../events/types.js';
6
- import { EXECUTION_PRINCIPLE_STRATEGY } from '../core/constants.js';
7
- import { GestaltPrinciple } from '../core/types.js';
8
1
  import { ExecuteSessionManager } from './session.js';
9
- import { EXECUTE_SYSTEM_PROMPT, EXECUTE_EXECUTION_SYSTEM_PROMPT, EXECUTE_EVALUATION_SYSTEM_PROMPT, EVOLVE_STRUCTURAL_FIX_SYSTEM_PROMPT, EVOLVE_CONTEXTUAL_SYSTEM_PROMPT, buildPlanningStepPrompt, buildTaskExecutionPrompt, buildContextualEvaluationPrompt, buildDriftRetrospectivePrompt, buildStructuralFixPrompt, buildContextualEvolvePrompt, buildReExecutionPrompt, } from './prompts.js';
10
- import { validateDAG } from './dag-validator.js';
11
- import { computeParallelGroups } from './parallel-groups.js';
12
- import { measureDrift } from './drift-detector.js';
13
- import { DRIFT_THRESHOLD } from '../core/constants.js';
14
- import { validateSpecPatch } from './spec-patch-validator.js';
15
- import { applySpecPatch } from './spec-patch-applier.js';
16
- import { identifyImpactedTasks } from './impact-identifier.js';
17
- import { checkTermination } from './termination-detector.js';
18
- import { mergeSystemPrompt } from '../agent/prompt-resolver.js';
19
- import { codeGraphEngine } from '../code-graph/index.js';
20
- import { classifyStagnation } from '../resilience/stagnation-detector.js';
21
- import { suggestPersona, buildLateralContext, buildEscalationContext, } from '../resilience/lateral.js';
22
- import { RoleMatchEngine } from '../agent/role-match-engine.js';
23
- import { RolePromptGenerator } from '../agent/role-prompt-generator.js';
24
- import { RoleConsensusEngine } from '../agent/role-consensus-engine.js';
25
- // ─── Helpers ─────────────────────────────────────────────────────
26
- function extractKeywords(text) {
27
- const stopWords = new Set([
28
- 'the',
29
- 'a',
30
- 'an',
31
- 'and',
32
- 'or',
33
- 'but',
34
- 'in',
35
- 'on',
36
- 'at',
37
- 'to',
38
- 'for',
39
- 'of',
40
- 'with',
41
- 'by',
42
- '—',
43
- '+',
44
- ]);
45
- return text
46
- .replace(/[^\w\s가-힣]/g, ' ')
47
- .split(/\s+/)
48
- .filter((w) => w.length > 2 && !stopWords.has(w.toLowerCase()))
49
- .slice(0, 5);
50
- }
51
- // ─── Engine ─────────────────────────────────────────────────────
2
+ import { PlanningOrchestrator } from './orchestrators/planning.js';
3
+ import { ExecutionOrchestrator } from './orchestrators/execution.js';
4
+ import { EvaluationOrchestrator } from './orchestrators/evaluation.js';
5
+ import { EvolutionOrchestrator } from './orchestrators/evolution.js';
6
+ // ─── Engine (thin facade) ──────────────────────────────────────
52
7
  export class PassthroughExecuteEngine {
53
- eventStore;
54
8
  sessionManager;
55
- agentRegistry;
56
- roleAgentRegistry;
9
+ planningOrch;
10
+ executionOrch;
11
+ evaluationOrch;
12
+ evolutionOrch;
57
13
  constructor(eventStore, agentRegistry, roleAgentRegistry) {
58
- this.eventStore = eventStore;
59
14
  this.sessionManager = new ExecuteSessionManager(eventStore);
60
15
  this.sessionManager.loadFromStore();
61
- this.agentRegistry = agentRegistry;
62
- this.roleAgentRegistry = roleAgentRegistry;
16
+ this.planningOrch = new PlanningOrchestrator(this.sessionManager, eventStore, agentRegistry, roleAgentRegistry);
17
+ this.executionOrch = new ExecutionOrchestrator(this.sessionManager, eventStore, agentRegistry, roleAgentRegistry);
18
+ this.evaluationOrch = new EvaluationOrchestrator(this.sessionManager, eventStore, agentRegistry, roleAgentRegistry);
19
+ this.evolutionOrch = new EvolutionOrchestrator(this.sessionManager, eventStore, agentRegistry, roleAgentRegistry);
63
20
  }
64
21
  getSessionManager() {
65
22
  return this.sessionManager;
66
23
  }
24
+ // ─── Planning ────────────────────────────────────────────────
67
25
  start(spec, opts = {}) {
68
- try {
69
- const session = this.sessionManager.create(spec, {
70
- codeGraphRepoRoot: opts.codeGraphRepoRoot,
71
- });
72
- const executeContext = this.buildExecuteContext(spec, 1, []);
73
- return ok({ session, executeContext });
74
- }
75
- catch (e) {
76
- return err(new ExecuteError(`Failed to start execute session: ${e instanceof Error ? e.message : String(e)}`));
77
- }
26
+ return this.planningOrch.start(spec, opts);
78
27
  }
79
28
  planStep(sessionId, stepResult) {
80
- try {
81
- const session = this.sessionManager.get(sessionId);
82
- if (session.status !== 'planning') {
83
- return err(new ExecuteError(`Session is not in planning state: ${session.status}`));
84
- }
85
- // Validate step order
86
- const expectedStep = session.planningSteps.length + 1;
87
- const expectedPrinciple = PLANNING_PRINCIPLE_SEQUENCE[expectedStep - 1];
88
- if (stepResult.principle !== expectedPrinciple) {
89
- return err(new InvalidPlanningStepError(`Expected principle "${expectedPrinciple}" for step ${expectedStep}, got "${stepResult.principle}"`));
90
- }
91
- // Validate step result content
92
- const validationError = this.validateStepResult(session, stepResult);
93
- if (validationError) {
94
- return err(new InvalidPlanningStepError(validationError));
95
- }
96
- // For Continuity step: server-side cross-validation
97
- if (stepResult.principle === 'continuity') {
98
- const crossValidation = this.crossValidateDAG(session, stepResult);
99
- if (crossValidation) {
100
- return err(new InvalidPlanningStepError(crossValidation));
101
- }
102
- }
103
- this.sessionManager.addPlanningStep(sessionId, stepResult);
104
- const isLastStep = session.planningSteps.length >= PLANNING_TOTAL_STEPS;
105
- if (isLastStep) {
106
- return ok({
107
- session: this.sessionManager.get(sessionId),
108
- isLastStep: true,
109
- });
110
- }
111
- const nextStep = session.planningSteps.length + 1;
112
- const executeContext = this.buildExecuteContext(session.spec, nextStep, session.planningSteps);
113
- return ok({
114
- session: this.sessionManager.get(sessionId),
115
- executeContext,
116
- isLastStep: false,
117
- });
118
- }
119
- catch (e) {
120
- if (e instanceof ExecuteSessionNotFoundError || e instanceof InvalidPlanningStepError) {
121
- return err(e);
122
- }
123
- return err(new ExecuteError(`Failed to process planning step: ${e instanceof Error ? e.message : String(e)}`));
124
- }
29
+ return this.planningOrch.planStep(sessionId, stepResult);
125
30
  }
126
31
  planComplete(sessionId) {
127
- try {
128
- const session = this.sessionManager.get(sessionId);
129
- if (session.planningSteps.length < PLANNING_TOTAL_STEPS) {
130
- return err(new ExecuteError(`Planning is not complete: ${session.planningSteps.length}/${PLANNING_TOTAL_STEPS} steps done`));
131
- }
132
- // Assemble ExecutionPlan from all steps
133
- const fgStep = session.planningSteps.find((s) => s.principle === 'figure_ground');
134
- const closureStep = session.planningSteps.find((s) => s.principle === 'closure');
135
- const proximityStep = session.planningSteps.find((s) => s.principle === 'proximity');
136
- const continuityStep = session.planningSteps.find((s) => s.principle === 'continuity');
137
- if (!fgStep || !closureStep || !proximityStep || !continuityStep) {
138
- return err(new ExecuteError('Missing planning step results'));
139
- }
140
- // Final DAG validation on server side
141
- const serverDAG = validateDAG(closureStep.atomicTasks, proximityStep.taskGroups);
142
- this.eventStore.append('execute', sessionId, EventType.EXECUTE_PLAN_VALIDATED, {
143
- callerValid: continuityStep.dagValidation.isValid,
144
- serverValid: serverDAG.isValid,
145
- });
146
- const parallelGroups = computeParallelGroups(closureStep.atomicTasks, serverDAG.topologicalOrder);
147
- const plan = {
148
- planId: randomUUID(),
149
- specId: session.specId,
150
- classifiedACs: fgStep.classifiedACs,
151
- atomicTasks: closureStep.atomicTasks,
152
- taskGroups: proximityStep.taskGroups,
153
- dagValidation: serverDAG,
154
- parallelGroups,
155
- createdAt: new Date().toISOString(),
156
- };
157
- this.sessionManager.completePlan(sessionId, plan);
158
- return ok({
159
- session: this.sessionManager.get(sessionId),
160
- executionPlan: plan,
161
- });
162
- }
163
- catch (e) {
164
- if (e instanceof ExecuteSessionNotFoundError) {
165
- return err(e);
166
- }
167
- return err(new ExecuteError(`Failed to complete plan: ${e instanceof Error ? e.message : String(e)}`));
168
- }
32
+ return this.planningOrch.planComplete(sessionId);
169
33
  }
170
- // ─── Execution Phase ──────────────────────────────────────────
34
+ // ─── Execution ───────────────────────────────────────────────
171
35
  startExecution(sessionId) {
172
- try {
173
- const session = this.sessionManager.get(sessionId);
174
- if (session.status !== 'plan_complete') {
175
- return err(new TaskExecutionError(`Cannot start execution: session status is "${session.status}", expected "plan_complete"`));
176
- }
177
- if (!session.executionPlan) {
178
- return err(new TaskExecutionError('No execution plan found'));
179
- }
180
- this.sessionManager.startExecution(sessionId);
181
- const taskContext = this.buildNextTaskContext(this.sessionManager.get(sessionId));
182
- return ok({
183
- session: this.sessionManager.get(sessionId),
184
- taskContext,
185
- allTasksCompleted: taskContext === null,
186
- });
187
- }
188
- catch (e) {
189
- if (e instanceof ExecuteSessionNotFoundError)
190
- return err(e);
191
- return err(new TaskExecutionError(`Failed to start execution: ${e instanceof Error ? e.message : String(e)}`));
192
- }
36
+ return this.executionOrch.startExecution(sessionId);
193
37
  }
194
38
  submitTaskResult(sessionId, taskResult, driftThreshold) {
195
- try {
196
- const session = this.sessionManager.get(sessionId);
197
- if (session.status !== 'executing') {
198
- return err(new TaskExecutionError(`Cannot submit task result: session status is "${session.status}", expected "executing"`));
199
- }
200
- if (!session.executionPlan) {
201
- return err(new TaskExecutionError('No execution plan found'));
202
- }
203
- // Validate taskId exists in plan
204
- const task = session.executionPlan.atomicTasks.find((t) => t.taskId === taskResult.taskId);
205
- if (!task) {
206
- return err(new TaskExecutionError(`Task "${taskResult.taskId}" not found in execution plan`));
207
- }
208
- this.sessionManager.addTaskResult(sessionId, taskResult);
209
- // Clear role state from previous role_match/role_consensus cycle
210
- this.sessionManager.clearRoleState(sessionId);
211
- // Drift Detection (only for completed tasks)
212
- let driftScore;
213
- let retrospectiveContext;
214
- if (taskResult.status === 'completed') {
215
- const threshold = driftThreshold ?? DRIFT_THRESHOLD;
216
- driftScore = measureDrift(session.spec, task, taskResult, threshold);
217
- this.sessionManager.addDriftScore(sessionId, driftScore);
218
- if (driftScore.thresholdExceeded) {
219
- this.eventStore.append('execute', sessionId, EventType.EXECUTE_DRIFT_RETROSPECTIVE, {
220
- taskId: taskResult.taskId,
221
- driftScore,
222
- });
223
- retrospectiveContext = {
224
- systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
225
- retrospectivePrompt: buildDriftRetrospectivePrompt(session.spec, task, taskResult, driftScore),
226
- driftScore,
227
- };
228
- }
229
- }
230
- const updatedSession = this.sessionManager.get(sessionId);
231
- const taskContext = this.buildNextTaskContext(updatedSession);
232
- const allTasksCompleted = taskContext === null;
233
- return ok({
234
- session: updatedSession,
235
- taskContext,
236
- allTasksCompleted,
237
- driftScore,
238
- retrospectiveContext,
239
- });
240
- }
241
- catch (e) {
242
- if (e instanceof ExecuteSessionNotFoundError)
243
- return err(e);
244
- return err(new TaskExecutionError(`Failed to submit task result: ${e instanceof Error ? e.message : String(e)}`));
245
- }
39
+ return this.executionOrch.submitTaskResult(sessionId, taskResult, driftThreshold);
246
40
  }
247
- // ─── Role Agent System ──────────────────────────────────────
248
- /**
249
- * role_match: 2-call passthrough pattern.
250
- * - Call 1 (no matchResult): Returns matchContext for current task
251
- * - Call 2 (with matchResult): Stores matches, returns perspectivePrompts
252
- */
253
41
  roleMatch(sessionId, matchResult) {
254
- try {
255
- const session = this.sessionManager.get(sessionId);
256
- if (session.status !== 'executing') {
257
- return err(new TaskExecutionError(`Cannot perform role match: session status is "${session.status}", expected "executing"`));
258
- }
259
- const currentTask = this.getCurrentTask(session);
260
- if (!currentTask) {
261
- return err(new TaskExecutionError('No pending task found for role matching'));
262
- }
263
- // Call 1: Return match context
264
- if (!matchResult) {
265
- if (!this.roleAgentRegistry) {
266
- return err(new ExecuteError('RoleAgentRegistry not configured'));
267
- }
268
- this.eventStore.append('execute', sessionId, EventType.ROLE_MATCH_STARTED, {
269
- taskId: currentTask.taskId,
270
- });
271
- const engine = new RoleMatchEngine();
272
- const matchContext = engine.generateMatchContext(currentTask.taskId, currentTask.title, currentTask.description, this.roleAgentRegistry.getAll());
273
- return ok({ session, matchContext });
274
- }
275
- // Call 2: Submit match results, return perspective prompts
276
- this.sessionManager.setRoleMatches(sessionId, currentTask.taskId, matchResult);
277
- if (matchResult.length === 0) {
278
- return ok({
279
- session: this.sessionManager.get(sessionId),
280
- perspectivePrompts: [],
281
- });
282
- }
283
- if (!this.roleAgentRegistry) {
284
- return err(new ExecuteError('RoleAgentRegistry not configured'));
285
- }
286
- const matchedAgents = matchResult
287
- .map((m) => this.roleAgentRegistry.getByName(m.agentName))
288
- .filter((a) => a !== undefined);
289
- const generator = new RolePromptGenerator();
290
- const perspectivePrompts = generator.generatePerspectivePrompts(currentTask.title, currentTask.description, matchedAgents);
291
- return ok({
292
- session: this.sessionManager.get(sessionId),
293
- perspectivePrompts,
294
- });
295
- }
296
- catch (e) {
297
- if (e instanceof ExecuteSessionNotFoundError)
298
- return err(e);
299
- return err(new ExecuteError(`Failed role match: ${e instanceof Error ? e.message : String(e)}`));
300
- }
42
+ return this.executionOrch.roleMatch(sessionId, matchResult);
301
43
  }
302
- /**
303
- * role_consensus: 2-call passthrough pattern.
304
- * - Call 1 (perspectives, no consensus): Returns synthesisContext
305
- * - Call 2 (consensus): Stores consensus, returns roleGuidance
306
- */
307
44
  roleConsensus(sessionId, perspectives, consensus) {
308
- try {
309
- const session = this.sessionManager.get(sessionId);
310
- if (session.status !== 'executing') {
311
- return err(new TaskExecutionError(`Cannot perform role consensus: session status is "${session.status}", expected "executing"`));
312
- }
313
- const currentTask = this.getCurrentTask(session);
314
- if (!currentTask) {
315
- return err(new TaskExecutionError('No pending task found for role consensus'));
316
- }
317
- // Call 1: Submit perspectives, return synthesis context
318
- if (perspectives && !consensus) {
319
- this.eventStore.append('execute', sessionId, EventType.ROLE_CONSENSUS_STARTED, {
320
- taskId: currentTask.taskId,
321
- perspectiveCount: perspectives.length,
322
- });
323
- const engine = new RoleConsensusEngine();
324
- const synthesisContext = engine.generateSynthesisContext(currentTask.title, currentTask.description, perspectives);
325
- return ok({ session, synthesisContext });
326
- }
327
- // Call 2: Submit consensus, store in session
328
- if (consensus) {
329
- this.sessionManager.setRoleConsensus(sessionId, currentTask.taskId, consensus);
330
- const roleGuidance = {
331
- agents: consensus.perspectives,
332
- consensus: consensus.consensus,
333
- conflictResolutions: consensus.conflictResolutions,
334
- };
335
- return ok({
336
- session: this.sessionManager.get(sessionId),
337
- roleGuidance,
338
- });
339
- }
340
- return err(new ExecuteError('Either perspectives or consensus must be provided'));
341
- }
342
- catch (e) {
343
- if (e instanceof ExecuteSessionNotFoundError)
344
- return err(e);
345
- return err(new ExecuteError(`Failed role consensus: ${e instanceof Error ? e.message : String(e)}`));
346
- }
45
+ return this.executionOrch.roleConsensus(sessionId, perspectives, consensus);
46
+ }
47
+ hydrateSuggestedFiles(context, repoRoot) {
48
+ return this.executionOrch.hydrateSuggestedFiles(context, repoRoot);
347
49
  }
348
- // ─── Evaluate Phase (2-Stage Pipeline) ──────────────────────
349
- /**
350
- * Call 1: Start evaluation → returns structural commands to run.
351
- */
50
+ // ─── Evaluation ──────────────────────────────────────────────
352
51
  startEvaluation(sessionId) {
353
- try {
354
- const session = this.sessionManager.get(sessionId);
355
- if (session.status !== 'executing') {
356
- return err(new EvaluationError(`Cannot start evaluation: session status is "${session.status}", expected "executing"`));
357
- }
358
- if (!session.executionPlan) {
359
- return err(new EvaluationError('No execution plan found'));
360
- }
361
- this.sessionManager.startStructuralEvaluation(sessionId);
362
- let commands = [
363
- { name: 'lint', command: 'npm run lint' },
364
- { name: 'build', command: 'npm run build' },
365
- { name: 'test', command: 'npm test' },
366
- ];
367
- // blast-radius 기반 테스트 필터링: codeGraphRepoRoot가 설정되고 DB가 존재할 때
368
- if (session.codeGraphRepoRoot && codeGraphEngine.dbExists(session.codeGraphRepoRoot)) {
369
- try {
370
- const blastResult = codeGraphEngine.blastRadius(session.codeGraphRepoRoot, {
371
- base: 'HEAD~1',
372
- });
373
- const testFiles = blastResult.impactedFiles.filter((f) => f.includes('.test.') || f.includes('.spec.') || f.includes('__tests__'));
374
- if (testFiles.length > 0) {
375
- commands = commands.map((cmd) => {
376
- if (cmd.name === 'test') {
377
- return { ...cmd, command: `${cmd.command} -- ${testFiles.join(' ')}` };
378
- }
379
- return cmd;
380
- });
381
- }
382
- else {
383
- // 변경된 테스트 파일 없음 → 테스트 스킵
384
- commands = commands.map((cmd) => {
385
- if (cmd.name === 'test') {
386
- return { ...cmd, command: 'echo "No affected tests"' };
387
- }
388
- return cmd;
389
- });
390
- }
391
- }
392
- catch {
393
- // blast-radius 실패 시 기존 전체 테스트로 fallback (graceful degradation)
394
- }
395
- }
396
- return ok({
397
- session: this.sessionManager.get(sessionId),
398
- stage: 'structural',
399
- structuralContext: {
400
- phase: 'evaluating',
401
- stage: 'structural',
402
- commands,
403
- message: 'Run these structural checks and submit results. Adapt commands to your project (e.g., pnpm/yarn). All must pass to proceed to contextual evaluation.',
404
- },
405
- });
406
- }
407
- catch (e) {
408
- if (e instanceof ExecuteSessionNotFoundError)
409
- return err(e);
410
- return err(new EvaluationError(`Failed to start evaluation: ${e instanceof Error ? e.message : String(e)}`));
411
- }
52
+ return this.evaluationOrch.startEvaluation(sessionId);
412
53
  }
413
- /**
414
- * Call 2: Submit structural results → returns contextual context or short-circuits.
415
- */
416
54
  submitStructuralResult(sessionId, structuralResult) {
417
- try {
418
- const session = this.sessionManager.get(sessionId);
419
- if (session.status !== 'executing' || session.evaluateStage !== 'structural') {
420
- return err(new EvaluationError(`Cannot submit structural result: expected stage "structural", got "${session.evaluateStage ?? 'none'}"`));
421
- }
422
- this.sessionManager.completeStructuralStage(sessionId, structuralResult);
423
- // Short-circuit if structural checks failed
424
- if (!structuralResult.allPassed) {
425
- const failedCommands = structuralResult.commands
426
- .filter((c) => c.exitCode !== 0)
427
- .map((c) => `${c.name} (exit ${c.exitCode})`)
428
- .join(', ');
429
- this.sessionManager.shortCircuitEvaluation(sessionId, `Structural checks failed: ${failedCommands}`);
430
- return ok({
431
- session: this.sessionManager.get(sessionId),
432
- stage: 'complete',
433
- shortCircuited: true,
434
- evaluationResult: this.sessionManager.get(sessionId).evaluationResult,
435
- });
436
- }
437
- // Structural passed → advance to contextual stage
438
- this.sessionManager.startContextualEvaluation(sessionId);
439
- const updatedSession = this.sessionManager.get(sessionId);
440
- const contextualContext = this.buildContextualEvaluateContext(updatedSession);
441
- return ok({
442
- session: updatedSession,
443
- stage: 'contextual',
444
- contextualContext,
445
- });
446
- }
447
- catch (e) {
448
- if (e instanceof ExecuteSessionNotFoundError)
449
- return err(e);
450
- return err(new EvaluationError(`Failed to submit structural result: ${e instanceof Error ? e.message : String(e)}`));
451
- }
55
+ return this.evaluationOrch.submitStructuralResult(sessionId, structuralResult);
452
56
  }
453
- /**
454
- * Call 3: Submit contextual evaluation result → completes session.
455
- */
456
57
  submitEvaluation(sessionId, evaluationResult) {
457
- try {
458
- const session = this.sessionManager.get(sessionId);
459
- if (session.status !== 'executing' || session.evaluateStage !== 'contextual') {
460
- return err(new EvaluationError(`Cannot submit evaluation: expected stage "contextual", got "${session.evaluateStage ?? 'none'}"`));
461
- }
462
- // Validate evaluation covers all ACs
463
- const acCount = session.spec.acceptanceCriteria.length;
464
- const verifiedIndices = new Set(evaluationResult.verifications.map((v) => v.acIndex));
465
- for (let i = 0; i < acCount; i++) {
466
- if (!verifiedIndices.has(i)) {
467
- return err(new EvaluationError(`AC index ${i} is not verified`));
468
- }
469
- }
470
- // Validate score ranges
471
- if (evaluationResult.overallScore < 0 || evaluationResult.overallScore > 1) {
472
- return err(new EvaluationError(`overallScore must be between 0 and 1, got ${evaluationResult.overallScore}`));
473
- }
474
- if (evaluationResult.goalAlignment < 0 || evaluationResult.goalAlignment > 1) {
475
- return err(new EvaluationError(`goalAlignment must be between 0 and 1, got ${evaluationResult.goalAlignment}`));
476
- }
477
- this.sessionManager.completeEvaluation(sessionId, evaluationResult);
478
- return ok({
479
- session: this.sessionManager.get(sessionId),
480
- stage: 'complete',
481
- evaluationResult,
482
- });
483
- }
484
- catch (e) {
485
- if (e instanceof ExecuteSessionNotFoundError)
486
- return err(e);
487
- return err(new EvaluationError(`Failed to submit evaluation: ${e instanceof Error ? e.message : String(e)}`));
488
- }
58
+ return this.evaluationOrch.submitEvaluation(sessionId, evaluationResult);
489
59
  }
60
+ // ─── Session accessors ───────────────────────────────────────
490
61
  getSession(sessionId) {
491
62
  return this.sessionManager.get(sessionId);
492
63
  }
493
64
  listSessions() {
494
65
  return this.sessionManager.list();
495
66
  }
496
- // ─── Evolution Loop ────────────────────────────────────────────
497
- /**
498
- * evolve_fix: Structural 실패 시 fix context를 반환하거나, fix 결과를 제출한다.
499
- * - fixTasks 없으면: fixContext 반환 (caller가 fix 생성)
500
- * - fixTasks 있으면: fix 기록 후 re-evaluate를 위한 상태 복원
501
- */
67
+ // ─── Evolution ───────────────────────────────────────────────
502
68
  startStructuralFix(sessionId, fixTasks) {
503
- try {
504
- const session = this.sessionManager.get(sessionId);
505
- // Call 1: Return fix context (no fixTasks submitted)
506
- if (!fixTasks) {
507
- if (!session.structuralResult || session.structuralResult.allPassed) {
508
- return err(new ExecuteError('No structural failures to fix'));
509
- }
510
- this.sessionManager.startStructuralFix(sessionId);
511
- const failedCommands = session.structuralResult.commands.filter((c) => c.exitCode !== 0);
512
- return ok({
513
- session: this.sessionManager.get(sessionId),
514
- fixContext: {
515
- systemPrompt: mergeSystemPrompt(EVOLVE_STRUCTURAL_FIX_SYSTEM_PROMPT, this.agentRegistry, 'evaluate'),
516
- fixPrompt: buildStructuralFixPrompt(session.spec, failedCommands, session.taskResults),
517
- phase: 'evolving',
518
- stage: 'fix',
519
- failedCommands,
520
- },
521
- });
522
- }
523
- // Call 2: Submit fix results
524
- this.sessionManager.completeStructuralFix(sessionId, fixTasks);
525
- // Reset evaluation state for re-evaluate
526
- const updated = this.sessionManager.get(sessionId);
527
- updated.evaluateStage = undefined;
528
- updated.structuralResult = undefined;
529
- updated.evaluationResult = undefined;
530
- updated.status = 'executing';
531
- updated.updatedAt = new Date().toISOString();
532
- return ok({
533
- session: updated,
534
- });
535
- }
536
- catch (e) {
537
- if (e instanceof ExecuteSessionNotFoundError)
538
- return err(e);
539
- return err(new ExecuteError(`Failed structural fix: ${e instanceof Error ? e.message : String(e)}`));
540
- }
69
+ return this.evolutionOrch.startStructuralFix(sessionId, fixTasks);
541
70
  }
542
- /**
543
- * evolve: Contextual evolution 시작.
544
- * - evaluationResult에서 gap 분석 후 evolveContext 반환
545
- * - caller가 SpecPatch를 생성
546
- * - terminateReason='caller'이면 즉시 종료
547
- */
548
71
  startContextualEvolve(sessionId, terminateReason) {
549
- try {
550
- const session = this.sessionManager.get(sessionId);
551
- // Caller-initiated termination
552
- if (terminateReason === 'caller') {
553
- this.sessionManager.terminate(sessionId, 'caller');
554
- return ok({
555
- session: this.sessionManager.get(sessionId),
556
- terminated: true,
557
- terminationReason: 'caller',
558
- });
559
- }
560
- if (!session.evaluationResult) {
561
- return err(new ExecuteError('No evaluation result found. Run evaluate first.'));
562
- }
563
- // Check termination conditions
564
- const termination = checkTermination({
565
- evolutionHistory: session.evolutionHistory,
566
- currentScore: session.evaluationResult.overallScore,
567
- currentGoalAlignment: session.evaluationResult.goalAlignment,
568
- structuralFixCount: this.countStructuralFixes(session),
569
- contextualCount: session.evolutionHistory.length,
570
- });
571
- if (termination) {
572
- // Success → terminate normally
573
- if (termination.reason === 'success') {
574
- this.sessionManager.terminate(sessionId, 'success');
575
- return ok({
576
- session: this.sessionManager.get(sessionId),
577
- terminated: true,
578
- terminationReason: 'success',
579
- });
580
- }
581
- // Non-success → lateral thinking
582
- const pattern = classifyStagnation({
583
- evolutionHistory: session.evolutionHistory,
584
- currentScore: session.evaluationResult.overallScore,
585
- termination,
586
- });
587
- const persona = suggestPersona(pattern, session.lateralTriedPersonas);
588
- if (persona) {
589
- this.sessionManager.startLateral(sessionId, persona, pattern);
590
- const lateralCtx = buildLateralContext(persona, pattern, session.spec, session.evaluationResult, session.evolutionHistory, session.lateralAttempts + 1);
591
- return ok({
592
- session: this.sessionManager.get(sessionId),
593
- lateralContext: lateralCtx,
594
- });
595
- }
596
- // All personas exhausted → human escalation
597
- this.sessionManager.terminate(sessionId, 'human_escalation');
598
- this.eventStore.append('execute', sessionId, EventType.EVOLVE_HUMAN_ESCALATION, {
599
- triedPersonas: session.lateralTriedPersonas,
600
- bestScore: Math.max(...session.evolutionHistory.map((g) => g.evaluationScore), session.evaluationResult.overallScore),
601
- });
602
- const escalation = buildEscalationContext(session.lateralTriedPersonas, session.evaluationResult, session.evolutionHistory);
603
- return ok({
604
- session: this.sessionManager.get(sessionId),
605
- humanEscalation: escalation,
606
- terminated: true,
607
- terminationReason: 'human_escalation',
608
- });
609
- }
610
- return ok({
611
- session,
612
- evolveContext: {
613
- systemPrompt: mergeSystemPrompt(EVOLVE_CONTEXTUAL_SYSTEM_PROMPT, this.agentRegistry, 'evaluate'),
614
- evolvePrompt: buildContextualEvolvePrompt(session.spec, session.evaluationResult, session.evolutionHistory),
615
- phase: 'evolving',
616
- stage: 'evolve',
617
- evaluationResult: session.evaluationResult,
618
- evolutionHistory: session.evolutionHistory,
619
- },
620
- });
621
- }
622
- catch (e) {
623
- if (e instanceof ExecuteSessionNotFoundError)
624
- return err(e);
625
- return err(new ExecuteError(`Failed contextual evolve: ${e instanceof Error ? e.message : String(e)}`));
626
- }
72
+ return this.evolutionOrch.startContextualEvolve(sessionId, terminateReason);
627
73
  }
628
- /**
629
- * evolve_patch: Spec 패치 제출 → 검증 → 적용 → impacted tasks 식별 → re-execute context 반환
630
- */
631
74
  submitSpecPatch(sessionId, patch) {
632
- try {
633
- const session = this.sessionManager.get(sessionId);
634
- if (!session.executionPlan) {
635
- return err(new ExecuteError('No execution plan found'));
636
- }
637
- // Validate patch
638
- const validation = validateSpecPatch(patch, session.spec);
639
- if (!validation.valid) {
640
- const msgs = validation.errors.map((e) => `${e.field}: ${e.message}`).join('; ');
641
- return err(new ExecuteError(`Invalid spec patch: ${msgs}`));
642
- }
643
- // Apply patch
644
- const generation = session.currentGeneration + 1;
645
- const { newSpec, delta } = applySpecPatch(session.spec, patch, generation);
646
- // Record generation snapshot BEFORE applying
647
- this.sessionManager.recordEvolutionGeneration(sessionId, {
648
- generation: session.currentGeneration,
649
- spec: session.spec,
650
- evaluationScore: session.evaluationResult?.overallScore ?? 0,
651
- goalAlignment: session.evaluationResult?.goalAlignment ?? 0,
652
- delta,
653
- });
654
- // Apply patch to session
655
- this.sessionManager.patchSpec(sessionId, patch, newSpec, delta);
656
- // Identify impacted tasks
657
- const driftThreshold = DRIFT_THRESHOLD;
658
- const impactedTaskIds = identifyImpactedTasks(session.executionPlan.atomicTasks, session.driftHistory, delta, driftThreshold);
659
- if (impactedTaskIds.length === 0) {
660
- // No tasks need re-execution, just re-evaluate
661
- return ok({
662
- session: this.sessionManager.get(sessionId),
663
- impactedTaskIds: [],
664
- });
665
- }
666
- // Start re-execution
667
- this.sessionManager.startReExecution(sessionId, impactedTaskIds);
668
- const updatedSession = this.sessionManager.get(sessionId);
669
- const reExecuteContext = this.buildReExecuteContext(updatedSession, impactedTaskIds, delta);
670
- return ok({
671
- session: updatedSession,
672
- reExecuteContext: reExecuteContext ?? undefined,
673
- impactedTaskIds,
674
- });
675
- }
676
- catch (e) {
677
- if (e instanceof ExecuteSessionNotFoundError)
678
- return err(e);
679
- return err(new ExecuteError(`Failed to submit spec patch: ${e instanceof Error ? e.message : String(e)}`));
680
- }
75
+ return this.evolutionOrch.submitSpecPatch(sessionId, patch);
681
76
  }
682
- /**
683
- * evolve_re_execute: Re-execution 중 태스크 결과 제출
684
- */
685
77
  submitReExecuteTaskResult(sessionId, taskResult) {
686
- try {
687
- const session = this.sessionManager.get(sessionId);
688
- if (!session.executionPlan) {
689
- return err(new ExecuteError('No execution plan found'));
690
- }
691
- // Validate task exists
692
- const task = session.executionPlan.atomicTasks.find((t) => t.taskId === taskResult.taskId);
693
- if (!task) {
694
- return err(new TaskExecutionError(`Task "${taskResult.taskId}" not found in execution plan`));
695
- }
696
- this.sessionManager.addEvolveTaskResult(sessionId, taskResult);
697
- const updatedSession = this.sessionManager.get(sessionId);
698
- const nextContext = this.buildNextReExecuteTaskContext(updatedSession);
699
- return ok({
700
- session: updatedSession,
701
- reExecuteContext: nextContext ?? undefined,
702
- allTasksCompleted: nextContext === null,
703
- });
704
- }
705
- catch (e) {
706
- if (e instanceof ExecuteSessionNotFoundError)
707
- return err(e);
708
- return err(new TaskExecutionError(`Failed to submit re-execute task result: ${e instanceof Error ? e.message : String(e)}`));
709
- }
78
+ return this.evolutionOrch.submitReExecuteTaskResult(sessionId, taskResult);
710
79
  }
711
- /**
712
- * evolve_lateral: 다음 lateral persona를 suggest하거나 human_escalation 반환.
713
- * evolve에서 자동 분기되지만, caller가 명시적으로 다음 persona를 요청할 때도 사용.
714
- */
715
80
  startLateralEvolve(sessionId) {
716
- try {
717
- const session = this.sessionManager.get(sessionId);
718
- if (!session.evaluationResult) {
719
- return err(new ExecuteError('No evaluation result found. Run evaluate first.'));
720
- }
721
- // Check termination again (might have succeeded after lateral re-execute)
722
- const termination = checkTermination({
723
- evolutionHistory: session.evolutionHistory,
724
- currentScore: session.evaluationResult.overallScore,
725
- currentGoalAlignment: session.evaluationResult.goalAlignment,
726
- structuralFixCount: this.countStructuralFixes(session),
727
- contextualCount: session.evolutionHistory.length,
728
- });
729
- if (termination?.reason === 'success') {
730
- this.sessionManager.terminate(sessionId, 'success');
731
- return ok({
732
- session: this.sessionManager.get(sessionId),
733
- terminated: true,
734
- terminationReason: 'success',
735
- });
736
- }
737
- // Classify stagnation for next persona suggestion
738
- const pattern = termination
739
- ? classifyStagnation({
740
- evolutionHistory: session.evolutionHistory,
741
- currentScore: session.evaluationResult.overallScore,
742
- termination,
743
- })
744
- : 'spinning'; // fallback if no termination yet
745
- const persona = suggestPersona(pattern, session.lateralTriedPersonas);
746
- if (persona) {
747
- this.sessionManager.startLateral(sessionId, persona, pattern);
748
- const lateralCtx = buildLateralContext(persona, pattern, session.spec, session.evaluationResult, session.evolutionHistory, session.lateralAttempts + 1);
749
- return ok({
750
- session: this.sessionManager.get(sessionId),
751
- lateralContext: lateralCtx,
752
- });
753
- }
754
- // All personas exhausted → human escalation
755
- this.sessionManager.terminate(sessionId, 'human_escalation');
756
- this.eventStore.append('execute', sessionId, EventType.EVOLVE_HUMAN_ESCALATION, {
757
- triedPersonas: session.lateralTriedPersonas,
758
- bestScore: Math.max(...session.evolutionHistory.map((g) => g.evaluationScore), session.evaluationResult.overallScore),
759
- });
760
- const escalation = buildEscalationContext(session.lateralTriedPersonas, session.evaluationResult, session.evolutionHistory);
761
- return ok({
762
- session: this.sessionManager.get(sessionId),
763
- humanEscalation: escalation,
764
- terminated: true,
765
- terminationReason: 'human_escalation',
766
- });
767
- }
768
- catch (e) {
769
- if (e instanceof ExecuteSessionNotFoundError)
770
- return err(e);
771
- return err(new ExecuteError(`Failed lateral evolve: ${e instanceof Error ? e.message : String(e)}`));
772
- }
81
+ return this.evolutionOrch.startLateralEvolve(sessionId);
773
82
  }
774
- /**
775
- * evolve_lateral_result: Lateral thinking 결과(specPatch) 제출.
776
- * completeLateral() 후 기존 submitSpecPatch() 위임.
777
- */
778
83
  submitLateralResult(sessionId, lateralResult) {
779
- try {
780
- // Validate session exists
781
- this.sessionManager.get(sessionId);
782
- // Complete lateral phase
783
- this.sessionManager.completeLateral(sessionId, lateralResult.persona, lateralResult.description);
784
- // Delegate to existing submitSpecPatch
785
- return this.submitSpecPatch(sessionId, lateralResult.specPatch);
786
- }
787
- catch (e) {
788
- if (e instanceof ExecuteSessionNotFoundError)
789
- return err(e);
790
- return err(new ExecuteError(`Failed to submit lateral result: ${e instanceof Error ? e.message : String(e)}`));
791
- }
792
- }
793
- countStructuralFixes(session) {
794
- // Count from event history: how many EVOLVE_STRUCTURAL_FIX_STARTED events
795
- const events = this.eventStore.replay('execute', session.sessionId);
796
- return events.filter((e) => e.eventType === EventType.EVOLVE_STRUCTURAL_FIX_STARTED).length;
797
- }
798
- buildReExecuteContext(session, impactedTaskIds, delta) {
799
- if (!session.executionPlan)
800
- return null;
801
- const plan = session.executionPlan;
802
- const completedIds = new Set(session.taskResults
803
- .filter((r) => r.status === 'completed' || r.status === 'skipped')
804
- .map((r) => r.taskId));
805
- // Find first impacted task that's not yet completed
806
- const topoOrder = plan.dagValidation.topologicalOrder;
807
- for (const taskId of topoOrder) {
808
- if (!impactedTaskIds.includes(taskId))
809
- continue;
810
- if (completedIds.has(taskId))
811
- continue;
812
- const task = plan.atomicTasks.find((t) => t.taskId === taskId);
813
- if (!task)
814
- continue;
815
- const depsResolved = task.dependsOn.every((dep) => completedIds.has(dep));
816
- if (!depsResolved)
817
- continue;
818
- const patchSummary = `Fields changed: ${delta.fieldsChanged.join(', ')}`;
819
- return {
820
- systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
821
- taskPrompt: buildReExecutionPrompt(task, session.spec, session.taskResults, patchSummary),
822
- phase: 'evolving',
823
- stage: 're_execute',
824
- currentTask: task,
825
- impactedTaskIds,
826
- patchSummary,
827
- };
828
- }
829
- return null;
830
- }
831
- buildNextReExecuteTaskContext(session) {
832
- if (!session.executionPlan)
833
- return null;
834
- // Find impacted tasks that are not yet completed
835
- const plan = session.executionPlan;
836
- const completedIds = new Set(session.taskResults
837
- .filter((r) => r.status === 'completed' || r.status === 'skipped')
838
- .map((r) => r.taskId));
839
- const topoOrder = plan.dagValidation.topologicalOrder;
840
- for (const taskId of topoOrder) {
841
- if (completedIds.has(taskId))
842
- continue;
843
- const task = plan.atomicTasks.find((t) => t.taskId === taskId);
844
- if (!task)
845
- continue;
846
- const depsResolved = task.dependsOn.every((dep) => completedIds.has(dep));
847
- if (!depsResolved)
848
- continue;
849
- const delta = session.evolutionHistory.length > 0
850
- ? session.evolutionHistory[session.evolutionHistory.length - 1].delta
851
- : { fieldsChanged: [] };
852
- const patchSummary = `Fields changed: ${delta.fieldsChanged.join(', ')}`;
853
- return {
854
- systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
855
- taskPrompt: buildReExecutionPrompt(task, session.spec, session.taskResults, patchSummary),
856
- phase: 'evolving',
857
- stage: 're_execute',
858
- currentTask: task,
859
- impactedTaskIds: [],
860
- patchSummary,
861
- };
862
- }
863
- return null;
864
- }
865
- // ─── Execution context builders ────────────────────────────────
866
- getCurrentTask(session) {
867
- if (!session.executionPlan)
868
- return null;
869
- const plan = session.executionPlan;
870
- const completedIds = new Set(session.taskResults
871
- .filter((r) => r.status === 'completed' || r.status === 'skipped')
872
- .map((r) => r.taskId));
873
- const failedIds = new Set(session.taskResults.filter((r) => r.status === 'failed').map((r) => r.taskId));
874
- const topoOrder = plan.dagValidation.topologicalOrder;
875
- for (const taskId of topoOrder) {
876
- if (completedIds.has(taskId) || failedIds.has(taskId))
877
- continue;
878
- const task = plan.atomicTasks.find((t) => t.taskId === taskId);
879
- if (!task)
880
- continue;
881
- const depsResolved = task.dependsOn.every((dep) => completedIds.has(dep) || failedIds.has(dep));
882
- if (depsResolved)
883
- return task;
884
- }
885
- return null;
886
- }
887
- buildNextTaskContext(session) {
888
- const nextTask = this.getCurrentTask(session);
889
- if (!nextTask)
890
- return null;
891
- const plan = session.executionPlan;
892
- const completedIds = new Set(session.taskResults
893
- .filter((r) => r.status === 'completed' || r.status === 'skipped')
894
- .map((r) => r.taskId));
895
- const failedIds = new Set(session.taskResults.filter((r) => r.status === 'failed').map((r) => r.taskId));
896
- // Find similar completed tasks (Similarity principle)
897
- const similarTasks = this.findSimilarTasks(nextTask, plan.atomicTasks, completedIds);
898
- const completedResults = session.taskResults.filter((r) => r.status === 'completed');
899
- const pendingTasks = plan.atomicTasks.filter((t) => !completedIds.has(t.taskId) && !failedIds.has(t.taskId) && t.taskId !== nextTask.taskId);
900
- // suggestedFiles 계산 (code-graph searchByKeywords 기반 — 동기 fallback)
901
- // Semantic/hybrid upgrade는 호출측(MCP handler 등)에서 hydrateTaskContextSuggestedFiles()로 수행
902
- let suggestedFiles;
903
- if (session.codeGraphRepoRoot && codeGraphEngine.dbExists(session.codeGraphRepoRoot)) {
904
- try {
905
- const keywords = extractKeywords(nextTask.title + ' ' + nextTask.description);
906
- const files = codeGraphEngine.searchByKeywords(session.codeGraphRepoRoot, keywords);
907
- suggestedFiles = files.slice(0, 10);
908
- }
909
- catch {
910
- // graceful fallback — suggestedFiles remains undefined
911
- }
912
- }
913
- const taskPrompt = buildTaskExecutionPrompt(nextTask, session.spec, completedResults, similarTasks, suggestedFiles);
914
- // Include roleGuidance if available from a previous role_match/role_consensus cycle
915
- const roleGuidance = session.roleConsensus
916
- ? {
917
- agents: session.roleConsensus.perspectives,
918
- consensus: session.roleConsensus.consensus,
919
- conflictResolutions: session.roleConsensus.conflictResolutions,
920
- }
921
- : undefined;
922
- return {
923
- systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
924
- taskPrompt,
925
- phase: 'executing',
926
- currentTask: nextTask,
927
- similarityStrategy: EXECUTION_PRINCIPLE_STRATEGY[GestaltPrinciple.SIMILARITY],
928
- pendingTasks,
929
- completedTaskIds: Array.from(completedIds),
930
- roleGuidance,
931
- suggestedFiles,
932
- };
933
- }
934
- // ─── Semantic Hydration ───────────────────────────────────────────
935
- /**
936
- * suggestedFiles를 searchByHybrid(키워드+의미론)로 업그레이드.
937
- * buildNextTaskContext()는 동기 유지, 호출측에서 필요 시 이 메서드로 보강한다.
938
- */
939
- async hydrateSuggestedFiles(context, repoRoot) {
940
- if (!codeGraphEngine.dbExists(repoRoot))
941
- return context;
942
- try {
943
- const query = extractKeywords(context.currentTask.title + ' ' + context.currentTask.description).join(' ');
944
- const files = await codeGraphEngine.searchByHybrid(repoRoot, query, 10);
945
- return { ...context, suggestedFiles: files };
946
- }
947
- catch {
948
- // semantic 실패 시 기존 keyword-based suggestedFiles 유지
949
- return context;
950
- }
951
- }
952
- findSimilarTasks(target, allTasks, completedIds) {
953
- return allTasks.filter((t) => {
954
- if (!completedIds.has(t.taskId))
955
- return false;
956
- if (t.taskId === target.taskId)
957
- return false;
958
- // Same complexity or overlapping sourceAC = similar
959
- const sharedAC = t.sourceAC.some((ac) => target.sourceAC.includes(ac));
960
- const sameComplexity = t.estimatedComplexity === target.estimatedComplexity;
961
- return sharedAC || sameComplexity;
962
- });
963
- }
964
- buildContextualEvaluateContext(session) {
965
- const plan = session.executionPlan;
966
- const evaluatePrompt = buildContextualEvaluationPrompt(session.spec, plan.classifiedACs, session.taskResults, session.structuralResult);
967
- return {
968
- systemPrompt: mergeSystemPrompt(EXECUTE_EVALUATION_SYSTEM_PROMPT, this.agentRegistry, 'evaluate'),
969
- evaluatePrompt,
970
- phase: 'evaluating',
971
- stage: 'contextual',
972
- spec: session.spec,
973
- taskResults: session.taskResults,
974
- classifiedACs: plan.classifiedACs,
975
- structuralResult: session.structuralResult,
976
- };
977
- }
978
- // ─── Validation helpers ───────────────────────────────────────
979
- validateStepResult(session, stepResult) {
980
- switch (stepResult.principle) {
981
- case 'figure_ground':
982
- return this.validateFigureGround(session.spec, stepResult);
983
- case 'closure':
984
- return this.validateClosure(session, stepResult);
985
- case 'proximity':
986
- return this.validateProximity(session, stepResult);
987
- case 'continuity':
988
- return null; // Cross-validated separately
989
- default:
990
- return `Unknown principle: ${stepResult.principle}`;
991
- }
992
- }
993
- validateFigureGround(spec, result) {
994
- const { classifiedACs } = result;
995
- if (!classifiedACs || classifiedACs.length === 0) {
996
- return 'classifiedACs is required and must not be empty';
997
- }
998
- const acCount = spec.acceptanceCriteria.length;
999
- const indices = new Set(classifiedACs.map((ac) => ac.acIndex));
1000
- // Check all ACs are classified
1001
- for (let i = 0; i < acCount; i++) {
1002
- if (!indices.has(i)) {
1003
- return `AC index ${i} is not classified`;
1004
- }
1005
- }
1006
- // Check for out-of-range indices
1007
- for (const ac of classifiedACs) {
1008
- if (ac.acIndex < 0 || ac.acIndex >= acCount) {
1009
- return `AC index ${ac.acIndex} is out of range (0-${acCount - 1})`;
1010
- }
1011
- }
1012
- return null;
1013
- }
1014
- validateClosure(session, result) {
1015
- const { atomicTasks } = result;
1016
- if (!atomicTasks || atomicTasks.length === 0) {
1017
- return 'atomicTasks is required and must not be empty';
1018
- }
1019
- if (atomicTasks.length > MAX_ATOMIC_TASKS) {
1020
- return `Too many atomic tasks: ${atomicTasks.length} (max ${MAX_ATOMIC_TASKS})`;
1021
- }
1022
- // Check taskId uniqueness
1023
- const taskIds = new Set();
1024
- for (const task of atomicTasks) {
1025
- if (taskIds.has(task.taskId)) {
1026
- return `Duplicate taskId: ${task.taskId}`;
1027
- }
1028
- taskIds.add(task.taskId);
1029
- }
1030
- // Check sourceAC references
1031
- const fgStep = session.planningSteps.find((s) => s.principle === 'figure_ground');
1032
- if (fgStep) {
1033
- const validIndices = new Set(fgStep.classifiedACs.map((ac) => ac.acIndex));
1034
- for (const task of atomicTasks) {
1035
- if (!task.isImplicit) {
1036
- for (const acIdx of task.sourceAC) {
1037
- if (!validIndices.has(acIdx)) {
1038
- return `Task "${task.taskId}" references invalid AC index ${acIdx}`;
1039
- }
1040
- }
1041
- }
1042
- }
1043
- }
1044
- // Check dependsOn references
1045
- for (const task of atomicTasks) {
1046
- for (const dep of task.dependsOn) {
1047
- if (!taskIds.has(dep)) {
1048
- return `Task "${task.taskId}" depends on non-existent task "${dep}"`;
1049
- }
1050
- }
1051
- }
1052
- return null;
1053
- }
1054
- validateProximity(session, result) {
1055
- const { taskGroups } = result;
1056
- if (!taskGroups || taskGroups.length === 0) {
1057
- return 'taskGroups is required and must not be empty';
1058
- }
1059
- if (taskGroups.length > MAX_TASK_GROUPS) {
1060
- return `Too many task groups: ${taskGroups.length} (max ${MAX_TASK_GROUPS})`;
1061
- }
1062
- // Get valid task IDs from Closure step
1063
- const closureStep = session.planningSteps.find((s) => s.principle === 'closure');
1064
- if (!closureStep) {
1065
- return 'Closure step must be completed before Proximity';
1066
- }
1067
- const validTaskIds = new Set(closureStep.atomicTasks.map((t) => t.taskId));
1068
- const assignedTaskIds = new Set();
1069
- // Check groupId uniqueness
1070
- const groupIds = new Set();
1071
- for (const group of taskGroups) {
1072
- if (groupIds.has(group.groupId)) {
1073
- return `Duplicate groupId: ${group.groupId}`;
1074
- }
1075
- groupIds.add(group.groupId);
1076
- for (const tid of group.taskIds) {
1077
- if (!validTaskIds.has(tid)) {
1078
- return `Group "${group.groupId}" references invalid task "${tid}"`;
1079
- }
1080
- if (assignedTaskIds.has(tid)) {
1081
- return `Task "${tid}" is assigned to multiple groups`;
1082
- }
1083
- assignedTaskIds.add(tid);
1084
- }
1085
- }
1086
- // Check all tasks are grouped
1087
- for (const tid of validTaskIds) {
1088
- if (!assignedTaskIds.has(tid)) {
1089
- return `Task "${tid}" is not assigned to any group`;
1090
- }
1091
- }
1092
- return null;
1093
- }
1094
- crossValidateDAG(session, continuityResult) {
1095
- const closureStep = session.planningSteps.find((s) => s.principle === 'closure');
1096
- const proximityStep = session.planningSteps.find((s) => s.principle === 'proximity');
1097
- if (!closureStep || !proximityStep) {
1098
- return 'Closure and Proximity steps must be completed before Continuity';
1099
- }
1100
- const serverDAG = validateDAG(closureStep.atomicTasks, proximityStep.taskGroups);
1101
- // If caller says valid but server finds issues, flag it
1102
- if (continuityResult.dagValidation.isValid && !serverDAG.isValid) {
1103
- const issues = [...(serverDAG.cycleDetails ?? []), ...(serverDAG.conflictDetails ?? [])].join('; ');
1104
- return `Server-side DAG validation disagrees: ${issues}`;
1105
- }
1106
- return null;
1107
- }
1108
- // ─── Context builder ──────────────────────────────────────────
1109
- buildExecuteContext(spec, stepNumber, previousSteps) {
1110
- const principle = PLANNING_PRINCIPLE_SEQUENCE[stepNumber - 1];
1111
- const planningPrompt = buildPlanningStepPrompt(spec, stepNumber, previousSteps);
1112
- return {
1113
- systemPrompt: mergeSystemPrompt(EXECUTE_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
1114
- planningPrompt,
1115
- currentPrinciple: principle,
1116
- principleStrategy: PLANNING_PRINCIPLE_STRATEGIES[principle],
1117
- phase: 'planning',
1118
- stepNumber,
1119
- totalSteps: PLANNING_TOTAL_STEPS,
1120
- spec,
1121
- previousSteps,
1122
- };
84
+ return this.evolutionOrch.submitLateralResult(sessionId, lateralResult);
1123
85
  }
1124
86
  }
1125
87
  //# sourceMappingURL=passthrough-engine.js.map