@tienne/gestalt 0.1.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 (288) hide show
  1. package/CLAUDE.md +203 -0
  2. package/README.md +60 -0
  3. package/agents/closure-completer/AGENT.md +32 -0
  4. package/agents/continuity-judge/AGENT.md +40 -0
  5. package/agents/ground-mapper/AGENT.md +31 -0
  6. package/agents/proximity-worker/AGENT.md +33 -0
  7. package/agents/similarity-crystallizer/AGENT.md +34 -0
  8. package/dist/agents/closure-completer/AGENT.md +32 -0
  9. package/dist/agents/continuity-judge/AGENT.md +40 -0
  10. package/dist/agents/ground-mapper/AGENT.md +31 -0
  11. package/dist/agents/proximity-worker/AGENT.md +33 -0
  12. package/dist/agents/similarity-crystallizer/AGENT.md +34 -0
  13. package/dist/bin/gestalt.d.ts +3 -0
  14. package/dist/bin/gestalt.d.ts.map +1 -0
  15. package/dist/bin/gestalt.js +5 -0
  16. package/dist/bin/gestalt.js.map +1 -0
  17. package/dist/skills/execute/SKILL.md +84 -0
  18. package/dist/skills/interview/SKILL.md +82 -0
  19. package/dist/skills/seed/SKILL.md +92 -0
  20. package/dist/skills/spec/SKILL.md +92 -0
  21. package/dist/src/agent/figural-router.d.ts +48 -0
  22. package/dist/src/agent/figural-router.d.ts.map +1 -0
  23. package/dist/src/agent/figural-router.js +55 -0
  24. package/dist/src/agent/figural-router.js.map +1 -0
  25. package/dist/src/agent/parser.d.ts +3 -0
  26. package/dist/src/agent/parser.d.ts.map +1 -0
  27. package/dist/src/agent/parser.js +32 -0
  28. package/dist/src/agent/parser.js.map +1 -0
  29. package/dist/src/agent/prompt-resolver.d.ts +17 -0
  30. package/dist/src/agent/prompt-resolver.d.ts.map +1 -0
  31. package/dist/src/agent/prompt-resolver.js +31 -0
  32. package/dist/src/agent/prompt-resolver.js.map +1 -0
  33. package/dist/src/agent/registry.d.ts +9 -0
  34. package/dist/src/agent/registry.d.ts.map +1 -0
  35. package/dist/src/agent/registry.js +22 -0
  36. package/dist/src/agent/registry.js.map +1 -0
  37. package/dist/src/cli/commands/interview.d.ts +2 -0
  38. package/dist/src/cli/commands/interview.d.ts.map +1 -0
  39. package/dist/src/cli/commands/interview.js +67 -0
  40. package/dist/src/cli/commands/interview.js.map +1 -0
  41. package/dist/src/cli/commands/seed.d.ts +4 -0
  42. package/dist/src/cli/commands/seed.d.ts.map +1 -0
  43. package/dist/src/cli/commands/seed.js +34 -0
  44. package/dist/src/cli/commands/seed.js.map +1 -0
  45. package/dist/src/cli/commands/serve.d.ts +2 -0
  46. package/dist/src/cli/commands/serve.d.ts.map +1 -0
  47. package/dist/src/cli/commands/serve.js +5 -0
  48. package/dist/src/cli/commands/serve.js.map +1 -0
  49. package/dist/src/cli/commands/spec.d.ts +4 -0
  50. package/dist/src/cli/commands/spec.d.ts.map +1 -0
  51. package/dist/src/cli/commands/spec.js +34 -0
  52. package/dist/src/cli/commands/spec.js.map +1 -0
  53. package/dist/src/cli/commands/status.d.ts +2 -0
  54. package/dist/src/cli/commands/status.d.ts.map +1 -0
  55. package/dist/src/cli/commands/status.js +66 -0
  56. package/dist/src/cli/commands/status.js.map +1 -0
  57. package/dist/src/cli/index.d.ts +3 -0
  58. package/dist/src/cli/index.d.ts.map +1 -0
  59. package/dist/src/cli/index.js +39 -0
  60. package/dist/src/cli/index.js.map +1 -0
  61. package/dist/src/core/config.d.ts +36 -0
  62. package/dist/src/core/config.d.ts.map +1 -0
  63. package/dist/src/core/config.js +65 -0
  64. package/dist/src/core/config.js.map +1 -0
  65. package/dist/src/core/constants.d.ts +34 -0
  66. package/dist/src/core/constants.d.ts.map +1 -0
  67. package/dist/src/core/constants.js +68 -0
  68. package/dist/src/core/constants.js.map +1 -0
  69. package/dist/src/core/errors.d.ts +50 -0
  70. package/dist/src/core/errors.d.ts.map +1 -0
  71. package/dist/src/core/errors.js +99 -0
  72. package/dist/src/core/errors.js.map +1 -0
  73. package/dist/src/core/log.d.ts +5 -0
  74. package/dist/src/core/log.d.ts.map +1 -0
  75. package/dist/src/core/log.js +7 -0
  76. package/dist/src/core/log.js.map +1 -0
  77. package/dist/src/core/result.d.ts +20 -0
  78. package/dist/src/core/result.d.ts.map +1 -0
  79. package/dist/src/core/result.js +43 -0
  80. package/dist/src/core/result.js.map +1 -0
  81. package/dist/src/core/types.d.ts +289 -0
  82. package/dist/src/core/types.d.ts.map +1 -0
  83. package/dist/src/core/types.js +15 -0
  84. package/dist/src/core/types.js.map +1 -0
  85. package/dist/src/events/store.d.ts +22 -0
  86. package/dist/src/events/store.d.ts.map +1 -0
  87. package/dist/src/events/store.js +130 -0
  88. package/dist/src/events/store.js.map +1 -0
  89. package/dist/src/events/types.d.ts +33 -0
  90. package/dist/src/events/types.d.ts.map +1 -0
  91. package/dist/src/events/types.js +35 -0
  92. package/dist/src/events/types.js.map +1 -0
  93. package/dist/src/execute/dag-validator.d.ts +7 -0
  94. package/dist/src/execute/dag-validator.d.ts.map +1 -0
  95. package/dist/src/execute/dag-validator.js +141 -0
  96. package/dist/src/execute/dag-validator.js.map +1 -0
  97. package/dist/src/execute/drift-detector.d.ts +11 -0
  98. package/dist/src/execute/drift-detector.d.ts.map +1 -0
  99. package/dist/src/execute/drift-detector.js +111 -0
  100. package/dist/src/execute/drift-detector.js.map +1 -0
  101. package/dist/src/execute/impact-identifier.d.ts +12 -0
  102. package/dist/src/execute/impact-identifier.d.ts.map +1 -0
  103. package/dist/src/execute/impact-identifier.js +60 -0
  104. package/dist/src/execute/impact-identifier.js.map +1 -0
  105. package/dist/src/execute/passthrough-engine.d.ts +181 -0
  106. package/dist/src/execute/passthrough-engine.d.ts.map +1 -0
  107. package/dist/src/execute/passthrough-engine.js +795 -0
  108. package/dist/src/execute/passthrough-engine.js.map +1 -0
  109. package/dist/src/execute/prompts.d.ts +19 -0
  110. package/dist/src/execute/prompts.d.ts.map +1 -0
  111. package/dist/src/execute/prompts.js +505 -0
  112. package/dist/src/execute/prompts.js.map +1 -0
  113. package/dist/src/execute/repository.d.ts +30 -0
  114. package/dist/src/execute/repository.d.ts.map +1 -0
  115. package/dist/src/execute/repository.js +248 -0
  116. package/dist/src/execute/repository.js.map +1 -0
  117. package/dist/src/execute/session.d.ts +34 -0
  118. package/dist/src/execute/session.d.ts.map +1 -0
  119. package/dist/src/execute/session.js +273 -0
  120. package/dist/src/execute/session.js.map +1 -0
  121. package/dist/src/execute/spec-patch-applier.d.ts +13 -0
  122. package/dist/src/execute/spec-patch-applier.d.ts.map +1 -0
  123. package/dist/src/execute/spec-patch-applier.js +83 -0
  124. package/dist/src/execute/spec-patch-applier.js.map +1 -0
  125. package/dist/src/execute/spec-patch-validator.d.ts +20 -0
  126. package/dist/src/execute/spec-patch-validator.d.ts.map +1 -0
  127. package/dist/src/execute/spec-patch-validator.js +60 -0
  128. package/dist/src/execute/spec-patch-validator.js.map +1 -0
  129. package/dist/src/execute/termination-detector.d.ts +20 -0
  130. package/dist/src/execute/termination-detector.d.ts.map +1 -0
  131. package/dist/src/execute/termination-detector.js +77 -0
  132. package/dist/src/execute/termination-detector.js.map +1 -0
  133. package/dist/src/gestalt/analyzer.d.ts +18 -0
  134. package/dist/src/gestalt/analyzer.d.ts.map +1 -0
  135. package/dist/src/gestalt/analyzer.js +59 -0
  136. package/dist/src/gestalt/analyzer.js.map +1 -0
  137. package/dist/src/gestalt/principles.d.ts +21 -0
  138. package/dist/src/gestalt/principles.d.ts.map +1 -0
  139. package/dist/src/gestalt/principles.js +66 -0
  140. package/dist/src/gestalt/principles.js.map +1 -0
  141. package/dist/src/index.d.ts +22 -0
  142. package/dist/src/index.d.ts.map +1 -0
  143. package/dist/src/index.js +22 -0
  144. package/dist/src/index.js.map +1 -0
  145. package/dist/src/interview/ambiguity.d.ts +8 -0
  146. package/dist/src/interview/ambiguity.d.ts.map +1 -0
  147. package/dist/src/interview/ambiguity.js +69 -0
  148. package/dist/src/interview/ambiguity.js.map +1 -0
  149. package/dist/src/interview/brownfield.d.ts +7 -0
  150. package/dist/src/interview/brownfield.d.ts.map +1 -0
  151. package/dist/src/interview/brownfield.js +28 -0
  152. package/dist/src/interview/brownfield.js.map +1 -0
  153. package/dist/src/interview/engine.d.ts +31 -0
  154. package/dist/src/interview/engine.d.ts.map +1 -0
  155. package/dist/src/interview/engine.js +110 -0
  156. package/dist/src/interview/engine.js.map +1 -0
  157. package/dist/src/interview/passthrough-engine.d.ts +52 -0
  158. package/dist/src/interview/passthrough-engine.d.ts.map +1 -0
  159. package/dist/src/interview/passthrough-engine.js +174 -0
  160. package/dist/src/interview/passthrough-engine.js.map +1 -0
  161. package/dist/src/interview/questions.d.ts +12 -0
  162. package/dist/src/interview/questions.d.ts.map +1 -0
  163. package/dist/src/interview/questions.js +54 -0
  164. package/dist/src/interview/questions.js.map +1 -0
  165. package/dist/src/interview/repository.d.ts +25 -0
  166. package/dist/src/interview/repository.d.ts.map +1 -0
  167. package/dist/src/interview/repository.js +102 -0
  168. package/dist/src/interview/repository.js.map +1 -0
  169. package/dist/src/interview/session.d.ts +22 -0
  170. package/dist/src/interview/session.d.ts.map +1 -0
  171. package/dist/src/interview/session.js +120 -0
  172. package/dist/src/interview/session.js.map +1 -0
  173. package/dist/src/llm/adapter.d.ts +8 -0
  174. package/dist/src/llm/adapter.d.ts.map +1 -0
  175. package/dist/src/llm/adapter.js +42 -0
  176. package/dist/src/llm/adapter.js.map +1 -0
  177. package/dist/src/llm/openai-adapter.d.ts +8 -0
  178. package/dist/src/llm/openai-adapter.d.ts.map +1 -0
  179. package/dist/src/llm/openai-adapter.js +45 -0
  180. package/dist/src/llm/openai-adapter.js.map +1 -0
  181. package/dist/src/llm/prompts.d.ts +15 -0
  182. package/dist/src/llm/prompts.d.ts.map +1 -0
  183. package/dist/src/llm/prompts.js +83 -0
  184. package/dist/src/llm/prompts.js.map +1 -0
  185. package/dist/src/llm/types.d.ts +21 -0
  186. package/dist/src/llm/types.d.ts.map +1 -0
  187. package/dist/src/llm/types.js +2 -0
  188. package/dist/src/llm/types.js.map +1 -0
  189. package/dist/src/mcp/schemas.d.ts +799 -0
  190. package/dist/src/mcp/schemas.d.ts.map +1 -0
  191. package/dist/src/mcp/schemas.js +151 -0
  192. package/dist/src/mcp/schemas.js.map +1 -0
  193. package/dist/src/mcp/server.d.ts +13 -0
  194. package/dist/src/mcp/server.d.ts.map +1 -0
  195. package/dist/src/mcp/server.js +255 -0
  196. package/dist/src/mcp/server.js.map +1 -0
  197. package/dist/src/mcp/tools/execute-passthrough.d.ts +4 -0
  198. package/dist/src/mcp/tools/execute-passthrough.d.ts.map +1 -0
  199. package/dist/src/mcp/tools/execute-passthrough.js +349 -0
  200. package/dist/src/mcp/tools/execute-passthrough.js.map +1 -0
  201. package/dist/src/mcp/tools/index.d.ts +4 -0
  202. package/dist/src/mcp/tools/index.d.ts.map +1 -0
  203. package/dist/src/mcp/tools/index.js +4 -0
  204. package/dist/src/mcp/tools/index.js.map +1 -0
  205. package/dist/src/mcp/tools/interview-passthrough.d.ts +4 -0
  206. package/dist/src/mcp/tools/interview-passthrough.d.ts.map +1 -0
  207. package/dist/src/mcp/tools/interview-passthrough.js +96 -0
  208. package/dist/src/mcp/tools/interview-passthrough.js.map +1 -0
  209. package/dist/src/mcp/tools/interview.d.ts +4 -0
  210. package/dist/src/mcp/tools/interview.d.ts.map +1 -0
  211. package/dist/src/mcp/tools/interview.js +85 -0
  212. package/dist/src/mcp/tools/interview.js.map +1 -0
  213. package/dist/src/mcp/tools/seed-passthrough.d.ts +5 -0
  214. package/dist/src/mcp/tools/seed-passthrough.d.ts.map +1 -0
  215. package/dist/src/mcp/tools/seed-passthrough.js +29 -0
  216. package/dist/src/mcp/tools/seed-passthrough.js.map +1 -0
  217. package/dist/src/mcp/tools/seed.d.ts +5 -0
  218. package/dist/src/mcp/tools/seed.d.ts.map +1 -0
  219. package/dist/src/mcp/tools/seed.js +19 -0
  220. package/dist/src/mcp/tools/seed.js.map +1 -0
  221. package/dist/src/mcp/tools/spec-passthrough.d.ts +5 -0
  222. package/dist/src/mcp/tools/spec-passthrough.d.ts.map +1 -0
  223. package/dist/src/mcp/tools/spec-passthrough.js +29 -0
  224. package/dist/src/mcp/tools/spec-passthrough.js.map +1 -0
  225. package/dist/src/mcp/tools/spec.d.ts +5 -0
  226. package/dist/src/mcp/tools/spec.d.ts.map +1 -0
  227. package/dist/src/mcp/tools/spec.js +19 -0
  228. package/dist/src/mcp/tools/spec.js.map +1 -0
  229. package/dist/src/mcp/tools/status.d.ts +4 -0
  230. package/dist/src/mcp/tools/status.d.ts.map +1 -0
  231. package/dist/src/mcp/tools/status.js +45 -0
  232. package/dist/src/mcp/tools/status.js.map +1 -0
  233. package/dist/src/registry/base-registry.d.ts +26 -0
  234. package/dist/src/registry/base-registry.d.ts.map +1 -0
  235. package/dist/src/registry/base-registry.js +82 -0
  236. package/dist/src/registry/base-registry.js.map +1 -0
  237. package/dist/src/seed/extractor.d.ts +15 -0
  238. package/dist/src/seed/extractor.d.ts.map +1 -0
  239. package/dist/src/seed/extractor.js +88 -0
  240. package/dist/src/seed/extractor.js.map +1 -0
  241. package/dist/src/seed/generator.d.ts +12 -0
  242. package/dist/src/seed/generator.d.ts.map +1 -0
  243. package/dist/src/seed/generator.js +66 -0
  244. package/dist/src/seed/generator.js.map +1 -0
  245. package/dist/src/seed/passthrough-generator.d.ts +31 -0
  246. package/dist/src/seed/passthrough-generator.d.ts.map +1 -0
  247. package/dist/src/seed/passthrough-generator.js +80 -0
  248. package/dist/src/seed/passthrough-generator.js.map +1 -0
  249. package/dist/src/seed/schema.d.ts +145 -0
  250. package/dist/src/seed/schema.d.ts.map +1 -0
  251. package/dist/src/seed/schema.js +37 -0
  252. package/dist/src/seed/schema.js.map +1 -0
  253. package/dist/src/skills/executor.d.ts +14 -0
  254. package/dist/src/skills/executor.d.ts.map +1 -0
  255. package/dist/src/skills/executor.js +17 -0
  256. package/dist/src/skills/executor.js.map +1 -0
  257. package/dist/src/skills/parser.d.ts +3 -0
  258. package/dist/src/skills/parser.d.ts.map +1 -0
  259. package/dist/src/skills/parser.js +37 -0
  260. package/dist/src/skills/parser.js.map +1 -0
  261. package/dist/src/skills/registry.d.ts +8 -0
  262. package/dist/src/skills/registry.d.ts.map +1 -0
  263. package/dist/src/skills/registry.js +19 -0
  264. package/dist/src/skills/registry.js.map +1 -0
  265. package/dist/src/skills/types.d.ts +19 -0
  266. package/dist/src/skills/types.d.ts.map +1 -0
  267. package/dist/src/skills/types.js +2 -0
  268. package/dist/src/skills/types.js.map +1 -0
  269. package/dist/src/spec/extractor.d.ts +15 -0
  270. package/dist/src/spec/extractor.d.ts.map +1 -0
  271. package/dist/src/spec/extractor.js +88 -0
  272. package/dist/src/spec/extractor.js.map +1 -0
  273. package/dist/src/spec/generator.d.ts +12 -0
  274. package/dist/src/spec/generator.d.ts.map +1 -0
  275. package/dist/src/spec/generator.js +66 -0
  276. package/dist/src/spec/generator.js.map +1 -0
  277. package/dist/src/spec/passthrough-generator.d.ts +34 -0
  278. package/dist/src/spec/passthrough-generator.d.ts.map +1 -0
  279. package/dist/src/spec/passthrough-generator.js +86 -0
  280. package/dist/src/spec/passthrough-generator.js.map +1 -0
  281. package/dist/src/spec/schema.d.ts +145 -0
  282. package/dist/src/spec/schema.d.ts.map +1 -0
  283. package/dist/src/spec/schema.js +37 -0
  284. package/dist/src/spec/schema.js.map +1 -0
  285. package/package.json +53 -0
  286. package/skills/execute/SKILL.md +84 -0
  287. package/skills/interview/SKILL.md +82 -0
  288. package/skills/spec/SKILL.md +92 -0
@@ -0,0 +1,795 @@
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
+ 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 { measureDrift } from './drift-detector.js';
12
+ import { DRIFT_THRESHOLD } from '../core/constants.js';
13
+ import { validateSpecPatch } from './spec-patch-validator.js';
14
+ import { applySpecPatch } from './spec-patch-applier.js';
15
+ import { identifyImpactedTasks } from './impact-identifier.js';
16
+ import { checkTermination } from './termination-detector.js';
17
+ import { mergeSystemPrompt } from '../agent/prompt-resolver.js';
18
+ // ─── Engine ─────────────────────────────────────────────────────
19
+ export class PassthroughExecuteEngine {
20
+ eventStore;
21
+ sessionManager;
22
+ agentRegistry;
23
+ constructor(eventStore, agentRegistry) {
24
+ this.eventStore = eventStore;
25
+ this.sessionManager = new ExecuteSessionManager(eventStore);
26
+ this.sessionManager.loadFromStore();
27
+ this.agentRegistry = agentRegistry;
28
+ }
29
+ start(spec) {
30
+ try {
31
+ const session = this.sessionManager.create(spec);
32
+ const executeContext = this.buildExecuteContext(spec, 1, []);
33
+ return ok({ session, executeContext });
34
+ }
35
+ catch (e) {
36
+ return err(new ExecuteError(`Failed to start execute session: ${e instanceof Error ? e.message : String(e)}`));
37
+ }
38
+ }
39
+ planStep(sessionId, stepResult) {
40
+ try {
41
+ const session = this.sessionManager.get(sessionId);
42
+ if (session.status !== 'planning') {
43
+ return err(new ExecuteError(`Session is not in planning state: ${session.status}`));
44
+ }
45
+ // Validate step order
46
+ const expectedStep = session.planningSteps.length + 1;
47
+ const expectedPrinciple = PLANNING_PRINCIPLE_SEQUENCE[expectedStep - 1];
48
+ if (stepResult.principle !== expectedPrinciple) {
49
+ return err(new InvalidPlanningStepError(`Expected principle "${expectedPrinciple}" for step ${expectedStep}, got "${stepResult.principle}"`));
50
+ }
51
+ // Validate step result content
52
+ const validationError = this.validateStepResult(session, stepResult);
53
+ if (validationError) {
54
+ return err(new InvalidPlanningStepError(validationError));
55
+ }
56
+ // For Continuity step: server-side cross-validation
57
+ if (stepResult.principle === 'continuity') {
58
+ const crossValidation = this.crossValidateDAG(session, stepResult);
59
+ if (crossValidation) {
60
+ return err(new InvalidPlanningStepError(crossValidation));
61
+ }
62
+ }
63
+ this.sessionManager.addPlanningStep(sessionId, stepResult);
64
+ const isLastStep = session.planningSteps.length >= PLANNING_TOTAL_STEPS;
65
+ if (isLastStep) {
66
+ return ok({
67
+ session: this.sessionManager.get(sessionId),
68
+ isLastStep: true,
69
+ });
70
+ }
71
+ const nextStep = session.planningSteps.length + 1;
72
+ const executeContext = this.buildExecuteContext(session.spec, nextStep, session.planningSteps);
73
+ return ok({
74
+ session: this.sessionManager.get(sessionId),
75
+ executeContext,
76
+ isLastStep: false,
77
+ });
78
+ }
79
+ catch (e) {
80
+ if (e instanceof ExecuteSessionNotFoundError || e instanceof InvalidPlanningStepError) {
81
+ return err(e);
82
+ }
83
+ return err(new ExecuteError(`Failed to process planning step: ${e instanceof Error ? e.message : String(e)}`));
84
+ }
85
+ }
86
+ planComplete(sessionId) {
87
+ try {
88
+ const session = this.sessionManager.get(sessionId);
89
+ if (session.planningSteps.length < PLANNING_TOTAL_STEPS) {
90
+ return err(new ExecuteError(`Planning is not complete: ${session.planningSteps.length}/${PLANNING_TOTAL_STEPS} steps done`));
91
+ }
92
+ // Assemble ExecutionPlan from all steps
93
+ const fgStep = session.planningSteps.find((s) => s.principle === 'figure_ground');
94
+ const closureStep = session.planningSteps.find((s) => s.principle === 'closure');
95
+ const proximityStep = session.planningSteps.find((s) => s.principle === 'proximity');
96
+ const continuityStep = session.planningSteps.find((s) => s.principle === 'continuity');
97
+ if (!fgStep || !closureStep || !proximityStep || !continuityStep) {
98
+ return err(new ExecuteError('Missing planning step results'));
99
+ }
100
+ // Final DAG validation on server side
101
+ const serverDAG = validateDAG(closureStep.atomicTasks, proximityStep.taskGroups);
102
+ this.eventStore.append('execute', sessionId, EventType.EXECUTE_PLAN_VALIDATED, {
103
+ callerValid: continuityStep.dagValidation.isValid,
104
+ serverValid: serverDAG.isValid,
105
+ });
106
+ const plan = {
107
+ planId: randomUUID(),
108
+ specId: session.specId,
109
+ classifiedACs: fgStep.classifiedACs,
110
+ atomicTasks: closureStep.atomicTasks,
111
+ taskGroups: proximityStep.taskGroups,
112
+ dagValidation: serverDAG,
113
+ createdAt: new Date().toISOString(),
114
+ };
115
+ this.sessionManager.completePlan(sessionId, plan);
116
+ return ok({
117
+ session: this.sessionManager.get(sessionId),
118
+ executionPlan: plan,
119
+ });
120
+ }
121
+ catch (e) {
122
+ if (e instanceof ExecuteSessionNotFoundError) {
123
+ return err(e);
124
+ }
125
+ return err(new ExecuteError(`Failed to complete plan: ${e instanceof Error ? e.message : String(e)}`));
126
+ }
127
+ }
128
+ // ─── Execution Phase ──────────────────────────────────────────
129
+ startExecution(sessionId) {
130
+ try {
131
+ const session = this.sessionManager.get(sessionId);
132
+ if (session.status !== 'plan_complete') {
133
+ return err(new TaskExecutionError(`Cannot start execution: session status is "${session.status}", expected "plan_complete"`));
134
+ }
135
+ if (!session.executionPlan) {
136
+ return err(new TaskExecutionError('No execution plan found'));
137
+ }
138
+ this.sessionManager.startExecution(sessionId);
139
+ const taskContext = this.buildNextTaskContext(this.sessionManager.get(sessionId));
140
+ return ok({
141
+ session: this.sessionManager.get(sessionId),
142
+ taskContext,
143
+ allTasksCompleted: taskContext === null,
144
+ });
145
+ }
146
+ catch (e) {
147
+ if (e instanceof ExecuteSessionNotFoundError)
148
+ return err(e);
149
+ return err(new TaskExecutionError(`Failed to start execution: ${e instanceof Error ? e.message : String(e)}`));
150
+ }
151
+ }
152
+ submitTaskResult(sessionId, taskResult, driftThreshold) {
153
+ try {
154
+ const session = this.sessionManager.get(sessionId);
155
+ if (session.status !== 'executing') {
156
+ return err(new TaskExecutionError(`Cannot submit task result: session status is "${session.status}", expected "executing"`));
157
+ }
158
+ if (!session.executionPlan) {
159
+ return err(new TaskExecutionError('No execution plan found'));
160
+ }
161
+ // Validate taskId exists in plan
162
+ const task = session.executionPlan.atomicTasks.find((t) => t.taskId === taskResult.taskId);
163
+ if (!task) {
164
+ return err(new TaskExecutionError(`Task "${taskResult.taskId}" not found in execution plan`));
165
+ }
166
+ this.sessionManager.addTaskResult(sessionId, taskResult);
167
+ // Drift Detection (only for completed tasks)
168
+ let driftScore;
169
+ let retrospectiveContext;
170
+ if (taskResult.status === 'completed') {
171
+ const threshold = driftThreshold ?? DRIFT_THRESHOLD;
172
+ driftScore = measureDrift(session.spec, task, taskResult, threshold);
173
+ this.sessionManager.addDriftScore(sessionId, driftScore);
174
+ if (driftScore.thresholdExceeded) {
175
+ this.eventStore.append('execute', sessionId, EventType.EXECUTE_DRIFT_RETROSPECTIVE, {
176
+ taskId: taskResult.taskId,
177
+ driftScore,
178
+ });
179
+ retrospectiveContext = {
180
+ systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
181
+ retrospectivePrompt: buildDriftRetrospectivePrompt(session.spec, task, taskResult, driftScore),
182
+ driftScore,
183
+ };
184
+ }
185
+ }
186
+ const updatedSession = this.sessionManager.get(sessionId);
187
+ const taskContext = this.buildNextTaskContext(updatedSession);
188
+ const allTasksCompleted = taskContext === null;
189
+ return ok({
190
+ session: updatedSession,
191
+ taskContext,
192
+ allTasksCompleted,
193
+ driftScore,
194
+ retrospectiveContext,
195
+ });
196
+ }
197
+ catch (e) {
198
+ if (e instanceof ExecuteSessionNotFoundError)
199
+ return err(e);
200
+ return err(new TaskExecutionError(`Failed to submit task result: ${e instanceof Error ? e.message : String(e)}`));
201
+ }
202
+ }
203
+ // ─── Evaluate Phase (2-Stage Pipeline) ──────────────────────
204
+ /**
205
+ * Call 1: Start evaluation → returns structural commands to run.
206
+ */
207
+ startEvaluation(sessionId) {
208
+ try {
209
+ const session = this.sessionManager.get(sessionId);
210
+ if (session.status !== 'executing') {
211
+ return err(new EvaluationError(`Cannot start evaluation: session status is "${session.status}", expected "executing"`));
212
+ }
213
+ if (!session.executionPlan) {
214
+ return err(new EvaluationError('No execution plan found'));
215
+ }
216
+ this.sessionManager.startStructuralEvaluation(sessionId);
217
+ const commands = [
218
+ { name: 'lint', command: 'npm run lint' },
219
+ { name: 'build', command: 'npm run build' },
220
+ { name: 'test', command: 'npm test' },
221
+ ];
222
+ return ok({
223
+ session: this.sessionManager.get(sessionId),
224
+ stage: 'structural',
225
+ structuralContext: {
226
+ phase: 'evaluating',
227
+ stage: 'structural',
228
+ commands,
229
+ 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.',
230
+ },
231
+ });
232
+ }
233
+ catch (e) {
234
+ if (e instanceof ExecuteSessionNotFoundError)
235
+ return err(e);
236
+ return err(new EvaluationError(`Failed to start evaluation: ${e instanceof Error ? e.message : String(e)}`));
237
+ }
238
+ }
239
+ /**
240
+ * Call 2: Submit structural results → returns contextual context or short-circuits.
241
+ */
242
+ submitStructuralResult(sessionId, structuralResult) {
243
+ try {
244
+ const session = this.sessionManager.get(sessionId);
245
+ if (session.status !== 'executing' || session.evaluateStage !== 'structural') {
246
+ return err(new EvaluationError(`Cannot submit structural result: expected stage "structural", got "${session.evaluateStage ?? 'none'}"`));
247
+ }
248
+ this.sessionManager.completeStructuralStage(sessionId, structuralResult);
249
+ // Short-circuit if structural checks failed
250
+ if (!structuralResult.allPassed) {
251
+ const failedCommands = structuralResult.commands
252
+ .filter((c) => c.exitCode !== 0)
253
+ .map((c) => `${c.name} (exit ${c.exitCode})`)
254
+ .join(', ');
255
+ this.sessionManager.shortCircuitEvaluation(sessionId, `Structural checks failed: ${failedCommands}`);
256
+ return ok({
257
+ session: this.sessionManager.get(sessionId),
258
+ stage: 'complete',
259
+ shortCircuited: true,
260
+ evaluationResult: this.sessionManager.get(sessionId).evaluationResult,
261
+ });
262
+ }
263
+ // Structural passed → advance to contextual stage
264
+ this.sessionManager.startContextualEvaluation(sessionId);
265
+ const updatedSession = this.sessionManager.get(sessionId);
266
+ const contextualContext = this.buildContextualEvaluateContext(updatedSession);
267
+ return ok({
268
+ session: updatedSession,
269
+ stage: 'contextual',
270
+ contextualContext,
271
+ });
272
+ }
273
+ catch (e) {
274
+ if (e instanceof ExecuteSessionNotFoundError)
275
+ return err(e);
276
+ return err(new EvaluationError(`Failed to submit structural result: ${e instanceof Error ? e.message : String(e)}`));
277
+ }
278
+ }
279
+ /**
280
+ * Call 3: Submit contextual evaluation result → completes session.
281
+ */
282
+ submitEvaluation(sessionId, evaluationResult) {
283
+ try {
284
+ const session = this.sessionManager.get(sessionId);
285
+ if (session.status !== 'executing' || session.evaluateStage !== 'contextual') {
286
+ return err(new EvaluationError(`Cannot submit evaluation: expected stage "contextual", got "${session.evaluateStage ?? 'none'}"`));
287
+ }
288
+ // Validate evaluation covers all ACs
289
+ const acCount = session.spec.acceptanceCriteria.length;
290
+ const verifiedIndices = new Set(evaluationResult.verifications.map((v) => v.acIndex));
291
+ for (let i = 0; i < acCount; i++) {
292
+ if (!verifiedIndices.has(i)) {
293
+ return err(new EvaluationError(`AC index ${i} is not verified`));
294
+ }
295
+ }
296
+ // Validate score ranges
297
+ if (evaluationResult.overallScore < 0 || evaluationResult.overallScore > 1) {
298
+ return err(new EvaluationError(`overallScore must be between 0 and 1, got ${evaluationResult.overallScore}`));
299
+ }
300
+ if (evaluationResult.goalAlignment < 0 || evaluationResult.goalAlignment > 1) {
301
+ return err(new EvaluationError(`goalAlignment must be between 0 and 1, got ${evaluationResult.goalAlignment}`));
302
+ }
303
+ this.sessionManager.completeEvaluation(sessionId, evaluationResult);
304
+ return ok({
305
+ session: this.sessionManager.get(sessionId),
306
+ stage: 'complete',
307
+ evaluationResult,
308
+ });
309
+ }
310
+ catch (e) {
311
+ if (e instanceof ExecuteSessionNotFoundError)
312
+ return err(e);
313
+ return err(new EvaluationError(`Failed to submit evaluation: ${e instanceof Error ? e.message : String(e)}`));
314
+ }
315
+ }
316
+ getSession(sessionId) {
317
+ return this.sessionManager.get(sessionId);
318
+ }
319
+ listSessions() {
320
+ return this.sessionManager.list();
321
+ }
322
+ // ─── Evolution Loop ────────────────────────────────────────────
323
+ /**
324
+ * evolve_fix: Structural 실패 시 fix context를 반환하거나, fix 결과를 제출한다.
325
+ * - fixTasks 없으면: fixContext 반환 (caller가 fix 생성)
326
+ * - fixTasks 있으면: fix 기록 후 re-evaluate를 위한 상태 복원
327
+ */
328
+ startStructuralFix(sessionId, fixTasks) {
329
+ try {
330
+ const session = this.sessionManager.get(sessionId);
331
+ // Call 1: Return fix context (no fixTasks submitted)
332
+ if (!fixTasks) {
333
+ if (!session.structuralResult || session.structuralResult.allPassed) {
334
+ return err(new ExecuteError('No structural failures to fix'));
335
+ }
336
+ this.sessionManager.startStructuralFix(sessionId);
337
+ const failedCommands = session.structuralResult.commands.filter((c) => c.exitCode !== 0);
338
+ return ok({
339
+ session: this.sessionManager.get(sessionId),
340
+ fixContext: {
341
+ systemPrompt: mergeSystemPrompt(EVOLVE_STRUCTURAL_FIX_SYSTEM_PROMPT, this.agentRegistry, 'evaluate'),
342
+ fixPrompt: buildStructuralFixPrompt(session.spec, failedCommands, session.taskResults),
343
+ phase: 'evolving',
344
+ stage: 'fix',
345
+ failedCommands,
346
+ },
347
+ });
348
+ }
349
+ // Call 2: Submit fix results
350
+ this.sessionManager.completeStructuralFix(sessionId, fixTasks);
351
+ // Reset evaluation state for re-evaluate
352
+ const updated = this.sessionManager.get(sessionId);
353
+ updated.evaluateStage = undefined;
354
+ updated.structuralResult = undefined;
355
+ updated.evaluationResult = undefined;
356
+ updated.status = 'executing';
357
+ updated.updatedAt = new Date().toISOString();
358
+ return ok({
359
+ session: updated,
360
+ });
361
+ }
362
+ catch (e) {
363
+ if (e instanceof ExecuteSessionNotFoundError)
364
+ return err(e);
365
+ return err(new ExecuteError(`Failed structural fix: ${e instanceof Error ? e.message : String(e)}`));
366
+ }
367
+ }
368
+ /**
369
+ * evolve: Contextual evolution 시작.
370
+ * - evaluationResult에서 gap 분석 후 evolveContext 반환
371
+ * - caller가 SpecPatch를 생성
372
+ * - terminateReason='caller'이면 즉시 종료
373
+ */
374
+ startContextualEvolve(sessionId, terminateReason) {
375
+ try {
376
+ const session = this.sessionManager.get(sessionId);
377
+ // Caller-initiated termination
378
+ if (terminateReason === 'caller') {
379
+ this.sessionManager.terminate(sessionId, 'caller');
380
+ return ok({
381
+ session: this.sessionManager.get(sessionId),
382
+ terminated: true,
383
+ terminationReason: 'caller',
384
+ });
385
+ }
386
+ if (!session.evaluationResult) {
387
+ return err(new ExecuteError('No evaluation result found. Run evaluate first.'));
388
+ }
389
+ // Check termination conditions
390
+ const termination = checkTermination({
391
+ evolutionHistory: session.evolutionHistory,
392
+ currentScore: session.evaluationResult.overallScore,
393
+ currentGoalAlignment: session.evaluationResult.goalAlignment,
394
+ structuralFixCount: this.countStructuralFixes(session),
395
+ contextualCount: session.evolutionHistory.length,
396
+ });
397
+ if (termination) {
398
+ this.sessionManager.terminate(sessionId, termination.reason);
399
+ return ok({
400
+ session: this.sessionManager.get(sessionId),
401
+ terminated: true,
402
+ terminationReason: termination.reason,
403
+ });
404
+ }
405
+ return ok({
406
+ session,
407
+ evolveContext: {
408
+ systemPrompt: mergeSystemPrompt(EVOLVE_CONTEXTUAL_SYSTEM_PROMPT, this.agentRegistry, 'evaluate'),
409
+ evolvePrompt: buildContextualEvolvePrompt(session.spec, session.evaluationResult, session.evolutionHistory),
410
+ phase: 'evolving',
411
+ stage: 'evolve',
412
+ evaluationResult: session.evaluationResult,
413
+ evolutionHistory: session.evolutionHistory,
414
+ },
415
+ });
416
+ }
417
+ catch (e) {
418
+ if (e instanceof ExecuteSessionNotFoundError)
419
+ return err(e);
420
+ return err(new ExecuteError(`Failed contextual evolve: ${e instanceof Error ? e.message : String(e)}`));
421
+ }
422
+ }
423
+ /**
424
+ * evolve_patch: Spec 패치 제출 → 검증 → 적용 → impacted tasks 식별 → re-execute context 반환
425
+ */
426
+ submitSpecPatch(sessionId, patch) {
427
+ try {
428
+ const session = this.sessionManager.get(sessionId);
429
+ if (!session.executionPlan) {
430
+ return err(new ExecuteError('No execution plan found'));
431
+ }
432
+ // Validate patch
433
+ const validation = validateSpecPatch(patch, session.spec);
434
+ if (!validation.valid) {
435
+ const msgs = validation.errors.map((e) => `${e.field}: ${e.message}`).join('; ');
436
+ return err(new ExecuteError(`Invalid spec patch: ${msgs}`));
437
+ }
438
+ // Apply patch
439
+ const generation = session.currentGeneration + 1;
440
+ const { newSpec, delta } = applySpecPatch(session.spec, patch, generation);
441
+ // Record generation snapshot BEFORE applying
442
+ this.sessionManager.recordEvolutionGeneration(sessionId, {
443
+ generation: session.currentGeneration,
444
+ spec: session.spec,
445
+ evaluationScore: session.evaluationResult?.overallScore ?? 0,
446
+ goalAlignment: session.evaluationResult?.goalAlignment ?? 0,
447
+ delta,
448
+ });
449
+ // Apply patch to session
450
+ this.sessionManager.patchSpec(sessionId, patch, newSpec, delta);
451
+ // Identify impacted tasks
452
+ const driftThreshold = DRIFT_THRESHOLD;
453
+ const impactedTaskIds = identifyImpactedTasks(session.executionPlan.atomicTasks, session.driftHistory, delta, driftThreshold);
454
+ if (impactedTaskIds.length === 0) {
455
+ // No tasks need re-execution, just re-evaluate
456
+ return ok({
457
+ session: this.sessionManager.get(sessionId),
458
+ impactedTaskIds: [],
459
+ });
460
+ }
461
+ // Start re-execution
462
+ this.sessionManager.startReExecution(sessionId, impactedTaskIds);
463
+ const updatedSession = this.sessionManager.get(sessionId);
464
+ const reExecuteContext = this.buildReExecuteContext(updatedSession, impactedTaskIds, delta);
465
+ return ok({
466
+ session: updatedSession,
467
+ reExecuteContext: reExecuteContext ?? undefined,
468
+ impactedTaskIds,
469
+ });
470
+ }
471
+ catch (e) {
472
+ if (e instanceof ExecuteSessionNotFoundError)
473
+ return err(e);
474
+ return err(new ExecuteError(`Failed to submit spec patch: ${e instanceof Error ? e.message : String(e)}`));
475
+ }
476
+ }
477
+ /**
478
+ * evolve_re_execute: Re-execution 중 태스크 결과 제출
479
+ */
480
+ submitReExecuteTaskResult(sessionId, taskResult) {
481
+ try {
482
+ const session = this.sessionManager.get(sessionId);
483
+ if (!session.executionPlan) {
484
+ return err(new ExecuteError('No execution plan found'));
485
+ }
486
+ // Validate task exists
487
+ const task = session.executionPlan.atomicTasks.find((t) => t.taskId === taskResult.taskId);
488
+ if (!task) {
489
+ return err(new TaskExecutionError(`Task "${taskResult.taskId}" not found in execution plan`));
490
+ }
491
+ this.sessionManager.addEvolveTaskResult(sessionId, taskResult);
492
+ const updatedSession = this.sessionManager.get(sessionId);
493
+ const nextContext = this.buildNextReExecuteTaskContext(updatedSession);
494
+ return ok({
495
+ session: updatedSession,
496
+ reExecuteContext: nextContext ?? undefined,
497
+ allTasksCompleted: nextContext === null,
498
+ });
499
+ }
500
+ catch (e) {
501
+ if (e instanceof ExecuteSessionNotFoundError)
502
+ return err(e);
503
+ return err(new TaskExecutionError(`Failed to submit re-execute task result: ${e instanceof Error ? e.message : String(e)}`));
504
+ }
505
+ }
506
+ countStructuralFixes(session) {
507
+ // Count from event history: how many EVOLVE_STRUCTURAL_FIX_STARTED events
508
+ const events = this.eventStore.replay('execute', session.sessionId);
509
+ return events.filter((e) => e.eventType === EventType.EVOLVE_STRUCTURAL_FIX_STARTED).length;
510
+ }
511
+ buildReExecuteContext(session, impactedTaskIds, delta) {
512
+ if (!session.executionPlan)
513
+ return null;
514
+ const plan = session.executionPlan;
515
+ const completedIds = new Set(session.taskResults
516
+ .filter((r) => r.status === 'completed' || r.status === 'skipped')
517
+ .map((r) => r.taskId));
518
+ // Find first impacted task that's not yet completed
519
+ const topoOrder = plan.dagValidation.topologicalOrder;
520
+ for (const taskId of topoOrder) {
521
+ if (!impactedTaskIds.includes(taskId))
522
+ continue;
523
+ if (completedIds.has(taskId))
524
+ continue;
525
+ const task = plan.atomicTasks.find((t) => t.taskId === taskId);
526
+ if (!task)
527
+ continue;
528
+ const depsResolved = task.dependsOn.every((dep) => completedIds.has(dep));
529
+ if (!depsResolved)
530
+ continue;
531
+ const patchSummary = `Fields changed: ${delta.fieldsChanged.join(', ')}`;
532
+ return {
533
+ systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
534
+ taskPrompt: buildReExecutionPrompt(task, session.spec, session.taskResults, patchSummary),
535
+ phase: 'evolving',
536
+ stage: 're_execute',
537
+ currentTask: task,
538
+ impactedTaskIds,
539
+ patchSummary,
540
+ };
541
+ }
542
+ return null;
543
+ }
544
+ buildNextReExecuteTaskContext(session) {
545
+ if (!session.executionPlan)
546
+ return null;
547
+ // Find impacted tasks that are not yet completed
548
+ const plan = session.executionPlan;
549
+ const completedIds = new Set(session.taskResults
550
+ .filter((r) => r.status === 'completed' || r.status === 'skipped')
551
+ .map((r) => r.taskId));
552
+ const topoOrder = plan.dagValidation.topologicalOrder;
553
+ for (const taskId of topoOrder) {
554
+ if (completedIds.has(taskId))
555
+ continue;
556
+ const task = plan.atomicTasks.find((t) => t.taskId === taskId);
557
+ if (!task)
558
+ continue;
559
+ const depsResolved = task.dependsOn.every((dep) => completedIds.has(dep));
560
+ if (!depsResolved)
561
+ continue;
562
+ const delta = session.evolutionHistory.length > 0
563
+ ? session.evolutionHistory[session.evolutionHistory.length - 1].delta
564
+ : { fieldsChanged: [] };
565
+ const patchSummary = `Fields changed: ${delta.fieldsChanged.join(', ')}`;
566
+ return {
567
+ systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
568
+ taskPrompt: buildReExecutionPrompt(task, session.spec, session.taskResults, patchSummary),
569
+ phase: 'evolving',
570
+ stage: 're_execute',
571
+ currentTask: task,
572
+ impactedTaskIds: [],
573
+ patchSummary,
574
+ };
575
+ }
576
+ return null;
577
+ }
578
+ // ─── Execution context builders ────────────────────────────────
579
+ buildNextTaskContext(session) {
580
+ if (!session.executionPlan)
581
+ return null;
582
+ const plan = session.executionPlan;
583
+ const completedIds = new Set(session.taskResults
584
+ .filter((r) => r.status === 'completed' || r.status === 'skipped')
585
+ .map((r) => r.taskId));
586
+ const failedIds = new Set(session.taskResults.filter((r) => r.status === 'failed').map((r) => r.taskId));
587
+ // Find next executable task (all dependencies satisfied, not yet done)
588
+ const topoOrder = plan.dagValidation.topologicalOrder;
589
+ let nextTask = null;
590
+ for (const taskId of topoOrder) {
591
+ if (completedIds.has(taskId) || failedIds.has(taskId))
592
+ continue;
593
+ const task = plan.atomicTasks.find((t) => t.taskId === taskId);
594
+ if (!task)
595
+ continue;
596
+ const depsResolved = task.dependsOn.every((dep) => completedIds.has(dep) || failedIds.has(dep));
597
+ if (depsResolved) {
598
+ nextTask = task;
599
+ break;
600
+ }
601
+ }
602
+ if (!nextTask)
603
+ return null;
604
+ // Find similar completed tasks (Similarity principle)
605
+ const similarTasks = this.findSimilarTasks(nextTask, plan.atomicTasks, completedIds);
606
+ const completedResults = session.taskResults.filter((r) => r.status === 'completed');
607
+ const pendingTasks = plan.atomicTasks.filter((t) => !completedIds.has(t.taskId) && !failedIds.has(t.taskId) && t.taskId !== nextTask.taskId);
608
+ const taskPrompt = buildTaskExecutionPrompt(nextTask, session.spec, completedResults, similarTasks);
609
+ return {
610
+ systemPrompt: mergeSystemPrompt(EXECUTE_EXECUTION_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
611
+ taskPrompt,
612
+ phase: 'executing',
613
+ currentTask: nextTask,
614
+ similarityStrategy: EXECUTION_PRINCIPLE_STRATEGY[GestaltPrinciple.SIMILARITY],
615
+ pendingTasks,
616
+ completedTaskIds: Array.from(completedIds),
617
+ };
618
+ }
619
+ findSimilarTasks(target, allTasks, completedIds) {
620
+ return allTasks.filter((t) => {
621
+ if (!completedIds.has(t.taskId))
622
+ return false;
623
+ if (t.taskId === target.taskId)
624
+ return false;
625
+ // Same complexity or overlapping sourceAC = similar
626
+ const sharedAC = t.sourceAC.some((ac) => target.sourceAC.includes(ac));
627
+ const sameComplexity = t.estimatedComplexity === target.estimatedComplexity;
628
+ return sharedAC || sameComplexity;
629
+ });
630
+ }
631
+ buildContextualEvaluateContext(session) {
632
+ const plan = session.executionPlan;
633
+ const evaluatePrompt = buildContextualEvaluationPrompt(session.spec, plan.classifiedACs, session.taskResults, session.structuralResult);
634
+ return {
635
+ systemPrompt: mergeSystemPrompt(EXECUTE_EVALUATION_SYSTEM_PROMPT, this.agentRegistry, 'evaluate'),
636
+ evaluatePrompt,
637
+ phase: 'evaluating',
638
+ stage: 'contextual',
639
+ spec: session.spec,
640
+ taskResults: session.taskResults,
641
+ classifiedACs: plan.classifiedACs,
642
+ structuralResult: session.structuralResult,
643
+ };
644
+ }
645
+ // ─── Validation helpers ───────────────────────────────────────
646
+ validateStepResult(session, stepResult) {
647
+ switch (stepResult.principle) {
648
+ case 'figure_ground':
649
+ return this.validateFigureGround(session.spec, stepResult);
650
+ case 'closure':
651
+ return this.validateClosure(session, stepResult);
652
+ case 'proximity':
653
+ return this.validateProximity(session, stepResult);
654
+ case 'continuity':
655
+ return null; // Cross-validated separately
656
+ default:
657
+ return `Unknown principle: ${stepResult.principle}`;
658
+ }
659
+ }
660
+ validateFigureGround(spec, result) {
661
+ const { classifiedACs } = result;
662
+ if (!classifiedACs || classifiedACs.length === 0) {
663
+ return 'classifiedACs is required and must not be empty';
664
+ }
665
+ const acCount = spec.acceptanceCriteria.length;
666
+ const indices = new Set(classifiedACs.map((ac) => ac.acIndex));
667
+ // Check all ACs are classified
668
+ for (let i = 0; i < acCount; i++) {
669
+ if (!indices.has(i)) {
670
+ return `AC index ${i} is not classified`;
671
+ }
672
+ }
673
+ // Check for out-of-range indices
674
+ for (const ac of classifiedACs) {
675
+ if (ac.acIndex < 0 || ac.acIndex >= acCount) {
676
+ return `AC index ${ac.acIndex} is out of range (0-${acCount - 1})`;
677
+ }
678
+ }
679
+ return null;
680
+ }
681
+ validateClosure(session, result) {
682
+ const { atomicTasks } = result;
683
+ if (!atomicTasks || atomicTasks.length === 0) {
684
+ return 'atomicTasks is required and must not be empty';
685
+ }
686
+ if (atomicTasks.length > MAX_ATOMIC_TASKS) {
687
+ return `Too many atomic tasks: ${atomicTasks.length} (max ${MAX_ATOMIC_TASKS})`;
688
+ }
689
+ // Check taskId uniqueness
690
+ const taskIds = new Set();
691
+ for (const task of atomicTasks) {
692
+ if (taskIds.has(task.taskId)) {
693
+ return `Duplicate taskId: ${task.taskId}`;
694
+ }
695
+ taskIds.add(task.taskId);
696
+ }
697
+ // Check sourceAC references
698
+ const fgStep = session.planningSteps.find((s) => s.principle === 'figure_ground');
699
+ if (fgStep) {
700
+ const validIndices = new Set(fgStep.classifiedACs.map((ac) => ac.acIndex));
701
+ for (const task of atomicTasks) {
702
+ if (!task.isImplicit) {
703
+ for (const acIdx of task.sourceAC) {
704
+ if (!validIndices.has(acIdx)) {
705
+ return `Task "${task.taskId}" references invalid AC index ${acIdx}`;
706
+ }
707
+ }
708
+ }
709
+ }
710
+ }
711
+ // Check dependsOn references
712
+ for (const task of atomicTasks) {
713
+ for (const dep of task.dependsOn) {
714
+ if (!taskIds.has(dep)) {
715
+ return `Task "${task.taskId}" depends on non-existent task "${dep}"`;
716
+ }
717
+ }
718
+ }
719
+ return null;
720
+ }
721
+ validateProximity(session, result) {
722
+ const { taskGroups } = result;
723
+ if (!taskGroups || taskGroups.length === 0) {
724
+ return 'taskGroups is required and must not be empty';
725
+ }
726
+ if (taskGroups.length > MAX_TASK_GROUPS) {
727
+ return `Too many task groups: ${taskGroups.length} (max ${MAX_TASK_GROUPS})`;
728
+ }
729
+ // Get valid task IDs from Closure step
730
+ const closureStep = session.planningSteps.find((s) => s.principle === 'closure');
731
+ if (!closureStep) {
732
+ return 'Closure step must be completed before Proximity';
733
+ }
734
+ const validTaskIds = new Set(closureStep.atomicTasks.map((t) => t.taskId));
735
+ const assignedTaskIds = new Set();
736
+ // Check groupId uniqueness
737
+ const groupIds = new Set();
738
+ for (const group of taskGroups) {
739
+ if (groupIds.has(group.groupId)) {
740
+ return `Duplicate groupId: ${group.groupId}`;
741
+ }
742
+ groupIds.add(group.groupId);
743
+ for (const tid of group.taskIds) {
744
+ if (!validTaskIds.has(tid)) {
745
+ return `Group "${group.groupId}" references invalid task "${tid}"`;
746
+ }
747
+ if (assignedTaskIds.has(tid)) {
748
+ return `Task "${tid}" is assigned to multiple groups`;
749
+ }
750
+ assignedTaskIds.add(tid);
751
+ }
752
+ }
753
+ // Check all tasks are grouped
754
+ for (const tid of validTaskIds) {
755
+ if (!assignedTaskIds.has(tid)) {
756
+ return `Task "${tid}" is not assigned to any group`;
757
+ }
758
+ }
759
+ return null;
760
+ }
761
+ crossValidateDAG(session, continuityResult) {
762
+ const closureStep = session.planningSteps.find((s) => s.principle === 'closure');
763
+ const proximityStep = session.planningSteps.find((s) => s.principle === 'proximity');
764
+ if (!closureStep || !proximityStep) {
765
+ return 'Closure and Proximity steps must be completed before Continuity';
766
+ }
767
+ const serverDAG = validateDAG(closureStep.atomicTasks, proximityStep.taskGroups);
768
+ // If caller says valid but server finds issues, flag it
769
+ if (continuityResult.dagValidation.isValid && !serverDAG.isValid) {
770
+ const issues = [
771
+ ...(serverDAG.cycleDetails ?? []),
772
+ ...(serverDAG.conflictDetails ?? []),
773
+ ].join('; ');
774
+ return `Server-side DAG validation disagrees: ${issues}`;
775
+ }
776
+ return null;
777
+ }
778
+ // ─── Context builder ──────────────────────────────────────────
779
+ buildExecuteContext(spec, stepNumber, previousSteps) {
780
+ const principle = PLANNING_PRINCIPLE_SEQUENCE[stepNumber - 1];
781
+ const planningPrompt = buildPlanningStepPrompt(spec, stepNumber, previousSteps);
782
+ return {
783
+ systemPrompt: mergeSystemPrompt(EXECUTE_SYSTEM_PROMPT, this.agentRegistry, 'execute'),
784
+ planningPrompt,
785
+ currentPrinciple: principle,
786
+ principleStrategy: PLANNING_PRINCIPLE_STRATEGIES[principle],
787
+ phase: 'planning',
788
+ stepNumber,
789
+ totalSteps: PLANNING_TOTAL_STEPS,
790
+ spec,
791
+ previousSteps,
792
+ };
793
+ }
794
+ }
795
+ //# sourceMappingURL=passthrough-engine.js.map