vibeman 0.0.1 → 0.0.3

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 (102) hide show
  1. package/dist/index.js +5 -7
  2. package/dist/runtime/api/.tsbuildinfo +1 -1
  3. package/dist/runtime/api/agent/agent-service.d.ts +18 -19
  4. package/dist/runtime/api/agent/agent-service.js +61 -58
  5. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
  6. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
  7. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +2 -0
  8. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
  9. package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
  10. package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
  11. package/dist/runtime/api/agent/parsers.d.ts +1 -0
  12. package/dist/runtime/api/agent/parsers.js +75 -8
  13. package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
  14. package/dist/runtime/api/agent/prompt-service.js +123 -14
  15. package/dist/runtime/api/agent/prompt-service.test.js +230 -0
  16. package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
  17. package/dist/runtime/api/agent/routing-policy.js +82 -132
  18. package/dist/runtime/api/agent/routing-policy.test.js +63 -0
  19. package/dist/runtime/api/api/routers/ai.d.ts +19 -7
  20. package/dist/runtime/api/api/routers/ai.js +9 -23
  21. package/dist/runtime/api/api/routers/executions.d.ts +4 -4
  22. package/dist/runtime/api/api/routers/executions.js +12 -21
  23. package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
  24. package/dist/runtime/api/api/routers/provider-config.js +252 -0
  25. package/dist/runtime/api/api/routers/tasks.d.ts +9 -9
  26. package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
  27. package/dist/runtime/api/api/routers/workflows.js +30 -27
  28. package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
  29. package/dist/runtime/api/api/routers/worktrees.js +11 -11
  30. package/dist/runtime/api/api/trpc.d.ts +16 -16
  31. package/dist/runtime/api/index.js +2 -10
  32. package/dist/runtime/api/lib/local-config.d.ts +245 -0
  33. package/dist/runtime/api/lib/local-config.js +288 -0
  34. package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
  35. package/dist/runtime/api/lib/provider-detection.js +244 -0
  36. package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
  37. package/dist/runtime/api/lib/server/bootstrap.js +197 -0
  38. package/dist/runtime/api/lib/server/project-root.js +24 -1
  39. package/dist/runtime/api/lib/trpc/server.d.ts +143 -30
  40. package/dist/runtime/api/lib/trpc/server.js +8 -8
  41. package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
  42. package/dist/runtime/api/router.d.ts +144 -31
  43. package/dist/runtime/api/router.js +9 -31
  44. package/dist/runtime/api/settings-service.js +51 -1
  45. package/dist/runtime/api/types/index.d.ts +8 -1
  46. package/dist/runtime/api/types/settings.d.ts +15 -2
  47. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
  48. package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
  49. package/dist/runtime/web/.next/BUILD_ID +1 -1
  50. package/dist/runtime/web/.next/app-build-manifest.json +19 -12
  51. package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
  52. package/dist/runtime/web/.next/build-manifest.json +2 -2
  53. package/dist/runtime/web/.next/prerender-manifest.json +10 -10
  54. package/dist/runtime/web/.next/routes-manifest.json +8 -0
  55. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
  56. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
  57. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
  58. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
  60. package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
  61. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
  62. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  63. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -1
  64. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
  65. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -1
  66. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
  67. package/dist/runtime/web/.next/server/app/index.html +2 -2
  68. package/dist/runtime/web/.next/server/app/index.rsc +6 -6
  69. package/dist/runtime/web/.next/server/app/page.js +21 -21
  70. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
  71. package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -1
  72. package/dist/runtime/web/.next/server/chunks/458.js +1 -1
  73. package/dist/runtime/web/.next/server/pages/404.html +2 -2
  74. package/dist/runtime/web/.next/server/pages/500.html +1 -1
  75. package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
  76. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
  77. package/dist/runtime/web/.next/static/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
  78. package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
  79. package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
  80. package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
  81. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
  82. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
  83. package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
  84. package/dist/runtime/web/.next/static/chunks/app/page-9fe7d75095b4ccec.js +1 -0
  85. package/dist/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +5 -1
  87. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
  88. package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
  89. package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
  90. package/dist/runtime/api/lib/markdown-utils.js +0 -282
  91. package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
  92. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
  93. package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
  94. package/dist/runtime/api/lib/tiptap-utils.js +0 -327
  95. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
  96. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
  97. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
  98. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
  99. package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
  100. /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
  101. /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
  102. /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → 5_15u1WQCxN1_eHZpldCv}/_ssgManifest.js +0 -0
@@ -313,19 +313,22 @@ export class VibingOrchestrator extends EventEmitter {
313
313
  }
314
314
  return res;
315
315
  }
316
- async reviewCode(taskId, reviewContext, options) {
317
- const res = await this.agentService.reviewCode(taskId, reviewContext, {
318
- provider: options?.provider,
319
- model: options?.model,
316
+ async aiReviewCode(taskId, reviewContext, options) {
317
+ const res = await this.agentService.aiReviewCode(taskId, reviewContext, {
318
+ overrides: options?.overrides,
319
+ workflowId: options?.workflowId,
320
+ executionId: options?.executionId,
320
321
  });
321
322
  const wfId = options?.workflowId;
322
323
  if (wfId) {
323
324
  const wf = this.workflows.get(wfId);
324
325
  if (wf) {
326
+ const timestamp = new Date().toISOString();
325
327
  wf.metadata = {
326
328
  ...wf.metadata,
327
- aiReviewResult: { ...res, timestamp: new Date().toISOString() },
329
+ aiReviewResult: { ...res, timestamp },
328
330
  };
331
+ wf.aiReviewResult = { ...res, timestamp };
329
332
  // Record execution ID under ai-reviewing for UI/observability
330
333
  if (res?.executionId) {
331
334
  wf.executionIds = wf.executionIds || {};
@@ -338,17 +341,15 @@ export class VibingOrchestrator extends EventEmitter {
338
341
  return res;
339
342
  }
340
343
  async aiMerge(taskId, options) {
341
- const res = await this.agentService.aiMerge(taskId, { baseBranch: options?.baseBranch }, { provider: options?.provider, model: options?.model });
342
- if (options?.workflowId) {
343
- const wf = this.workflows.get(options.workflowId);
344
- if (wf) {
345
- wf.executionIds = wf.executionIds || {};
346
- const list = wf.executionIds['merging'] || [];
347
- wf.executionIds['merging'] = [...list, res.executionId];
348
- await this.saveWorkflow(wf);
349
- }
350
- }
351
- return res;
344
+ const overrides = options?.provider || options?.model
345
+ ? { provider: options?.provider, model: options?.model }
346
+ : undefined;
347
+ return await this.agentService.aiMerge(taskId, {
348
+ baseBranch: options?.baseBranch,
349
+ workflowId: options?.workflowId,
350
+ executionId: options?.executionId,
351
+ overrides,
352
+ });
352
353
  }
353
354
  // Worktree façade
354
355
  async listWorktrees() {
@@ -702,27 +703,7 @@ export class VibingOrchestrator extends EventEmitter {
702
703
  metrics.totalDurationMs = Object.values(metrics.durationsMs || {}).reduce((a, b) => a + (b || 0), 0);
703
704
  }
704
705
  workflow.lastUpdatedAt = now;
705
- // If entering awaiting-review, add a real timeline item so the UI
706
- // can expose review controls (instead of a placeholder that hides actions)
707
- if (newPhase === 'awaiting-review') {
708
- const hasReal = Array.isArray(workflow.timeline)
709
- ? workflow.timeline.some((t) => t.phase === 'awaiting-review' && !t.placeholder)
710
- : false;
711
- if (!hasReal) {
712
- workflow.timeline = this.buildTimeline(workflow, {
713
- id: `awaiting-review:${now}`,
714
- label: PHASE_LABELS['awaiting-review'],
715
- phase: 'awaiting-review',
716
- startTime: now,
717
- });
718
- }
719
- else {
720
- workflow.timeline = this.computeVisibleTimeline(workflow);
721
- }
722
- }
723
- else {
724
- workflow.timeline = this.computeVisibleTimeline(workflow);
725
- }
706
+ workflow.timeline = this.computeVisibleTimeline(workflow);
726
707
  log.info('Workflow phase transition', { workflowId, oldPhase, newPhase }, 'vibing-orchestrator');
727
708
  await this.saveWorkflow(workflow);
728
709
  this.emit('workflowPhaseChanged', { workflowId, oldPhase, newPhase, workflow });
@@ -758,6 +739,14 @@ export class VibingOrchestrator extends EventEmitter {
758
739
  (workflow.failureContext?.atPhase
759
740
  ? `Retry after ${workflow.failureContext.atPhase} failure`
760
741
  : undefined);
742
+ const aiReviewSource = workflow.aiReviewResult || workflow.metadata?.aiReviewResult;
743
+ const aiReviewContext = aiReviewSource
744
+ ? {
745
+ summary: aiReviewSource.reviewSummary,
746
+ recommendations: aiReviewSource.recommendations,
747
+ score: aiReviewSource.qualityScore,
748
+ }
749
+ : undefined;
761
750
  // Prepare to capture executionId from event, then start execution
762
751
  const createdPromise = this.waitForExecutionCreated(workflow.taskId, workflowId, 20000);
763
752
  // Fire-and-forget; attach catch to prevent unhandled rejection from crashing process
@@ -769,10 +758,12 @@ export class VibingOrchestrator extends EventEmitter {
769
758
  previousExecutionId: previousExecId,
770
759
  failurePhase: workflow.failureContext?.atPhase,
771
760
  qualityResults: workflow.qualityResults,
761
+ aiReview: aiReviewContext,
772
762
  attempt,
773
763
  }
774
764
  : undefined,
775
765
  providerOverride: providerOverride || workflow.metadata?.aiRoutingOverrides?.execute_task,
766
+ workflowConfig: workflow.metadata,
776
767
  })
777
768
  .catch((err) => log.error('Agent execution failed to start (implementation phase)', err, 'vibing-orchestrator'));
778
769
  const executionId = await createdPromise;
@@ -851,29 +842,26 @@ export class VibingOrchestrator extends EventEmitter {
851
842
  await this.executeImplementation(workflowId);
852
843
  return;
853
844
  case 'validating':
854
- await this.transitionToPhase(workflowId, 'validating');
855
845
  await this.executeValidation(workflowId);
856
846
  return;
857
847
  case 'ai-reviewing':
858
- await this.transitionToPhase(workflowId, 'ai-reviewing');
859
848
  await this.executeAiReviewPhase(workflowId);
860
849
  return;
850
+ case 'awaiting-review':
851
+ await this.executeAwaitingReview(workflowId);
852
+ return;
861
853
  case 'merging':
862
- await this.transitionToPhase(workflowId, 'merging');
863
854
  await this.executeMerge(workflowId);
864
855
  return;
865
856
  case 'approved':
866
857
  // Failure after approval typically means merge failed; try merging again
867
- await this.transitionToPhase(workflowId, 'approved');
868
858
  await this.executeMerge(workflowId);
869
859
  return;
870
860
  case 'cleaning':
871
- await this.transitionToPhase(workflowId, 'cleaning');
872
861
  await this.executeCleanup(workflowId);
873
862
  return;
874
863
  default:
875
864
  // For other phases, restart implementation as a safe default
876
- await this.transitionToPhase(workflowId, 'implementing');
877
865
  await this.executeImplementation(workflowId);
878
866
  return;
879
867
  }
@@ -944,31 +932,39 @@ export class VibingOrchestrator extends EventEmitter {
944
932
  /**
945
933
  * Perform AI code review of implemented changes
946
934
  */
947
- async performAICodeReview(workflowId) {
935
+ async performAICodeReview(workflowId, providedExecutionId) {
948
936
  const workflow = this.workflows.get(workflowId);
949
937
  if (!workflow)
950
- return false;
938
+ return { passed: false };
951
939
  try {
952
940
  log.info('Running AI code review', { taskId: workflow.taskId }, 'vibing-orchestrator');
953
941
  // Get worktree info for the task
954
942
  const worktree = this.agentService.getWorktreeInfo(workflow.taskId);
955
943
  if (!worktree) {
956
944
  log.warn('No worktree found for AI code review, skipping', undefined, 'vibing-orchestrator');
957
- return true; // do not fail if worktree missing
945
+ return { passed: true, executionId: providedExecutionId };
958
946
  }
959
947
  // Execute AI code review via agent service
960
- const review = await this.agentService.reviewCode(workflow.taskId, 'Automated AI code review triggered by workflow orchestrator', workflow.metadata?.aiRoutingOverrides?.ai_codereview);
948
+ const review = await this.agentService.aiReviewCode(workflow.taskId, 'Automated AI code review triggered by workflow orchestrator', {
949
+ overrides: workflow.metadata?.aiRoutingOverrides?.ai_codereview,
950
+ executionId: providedExecutionId,
951
+ workflowId,
952
+ });
961
953
  // Persist review result into workflow metadata
962
954
  const timestamp = new Date().toISOString();
963
955
  workflow.metadata = {
964
956
  ...workflow.metadata,
965
957
  aiReviewResult: { ...review, timestamp },
966
958
  };
959
+ workflow.aiReviewResult = { ...review, timestamp };
967
960
  // Track execution ID under ai-reviewing for observability
968
- if (review?.executionId) {
961
+ const executionId = providedExecutionId ?? review?.executionId;
962
+ if (executionId) {
969
963
  workflow.executionIds = workflow.executionIds || {};
970
964
  const list = workflow.executionIds['ai-reviewing'] || [];
971
- workflow.executionIds['ai-reviewing'] = [...list, review.executionId];
965
+ if (!list.includes(executionId)) {
966
+ workflow.executionIds['ai-reviewing'] = [...list, executionId];
967
+ }
972
968
  }
973
969
  await this.saveWorkflow(workflow);
974
970
  // Gate by minimum score from settings
@@ -977,10 +973,11 @@ export class VibingOrchestrator extends EventEmitter {
977
973
  const msg = `AI code review score too low (${review.qualityScore} < ${minScore})`;
978
974
  log.warn('AI code review score too low', { score: review.qualityScore, minScore }, 'vibing-orchestrator');
979
975
  workflow.error = msg;
980
- return false;
976
+ return { passed: false, executionId };
981
977
  }
982
978
  log.info('AI code review passed', { score: review.qualityScore }, 'vibing-orchestrator');
983
- return true;
979
+ workflow.error = undefined;
980
+ return { passed: true, executionId };
984
981
  }
985
982
  catch (error) {
986
983
  log.warn('AI code review failed', error, 'vibing-orchestrator');
@@ -989,7 +986,7 @@ export class VibingOrchestrator extends EventEmitter {
989
986
  if (workflowRef) {
990
987
  workflowRef.error = `AI code review failed: ${msg}`;
991
988
  }
992
- return false;
989
+ return { passed: false, executionId: providedExecutionId };
993
990
  }
994
991
  }
995
992
  /**
@@ -999,26 +996,42 @@ export class VibingOrchestrator extends EventEmitter {
999
996
  const workflow = this.workflows.get(workflowId);
1000
997
  if (!workflow)
1001
998
  return;
999
+ await this.transitionToPhase(workflowId, 'ai-reviewing');
1002
1000
  try {
1003
- const before = workflow.executionIds?.['ai-reviewing']?.length || 0;
1004
- const passed = await this.performAICodeReview(workflowId);
1005
- // If an executionId was recorded for ai-reviewing, add instantaneous timeline entry
1006
- const after = workflow.executionIds?.['ai-reviewing']?.length || 0;
1007
- if (after > before) {
1008
- const execId = (workflow.executionIds?.['ai-reviewing'] || [])[after - 1];
1009
- workflow.timeline = this.buildTimeline(workflow, {
1010
- id: execId,
1011
- label: after > 1 ? `AI Review – Retry #${after}` : 'AI Review',
1012
- phase: 'ai-reviewing',
1013
- attempt: after,
1014
- executionId: execId,
1015
- startTime: new Date().toISOString(),
1016
- endTime: new Date().toISOString(),
1017
- reason: !passed
1018
- ? workflow.error || 'AI review did not pass the quality threshold'
1019
- : undefined,
1020
- });
1021
- await this.saveWorkflow(workflow);
1001
+ const attemptList = workflow.executionIds?.['ai-reviewing'] || [];
1002
+ const attempt = attemptList.length + 1;
1003
+ const execId = generateId('review');
1004
+ workflow.executionIds = workflow.executionIds || {};
1005
+ const aiList = workflow.executionIds['ai-reviewing'] || [];
1006
+ workflow.executionIds['ai-reviewing'] = [...aiList, execId];
1007
+ workflow.timeline = this.buildTimeline(workflow, {
1008
+ id: execId,
1009
+ label: attempt > 1 ? `AI Review – Retry #${attempt}` : 'AI Review',
1010
+ phase: 'ai-reviewing',
1011
+ attempt,
1012
+ executionId: execId,
1013
+ startTime: new Date().toISOString(),
1014
+ });
1015
+ await this.saveWorkflow(workflow);
1016
+ this.emit('workflowExecutionStarted', {
1017
+ workflowId,
1018
+ executionId: execId,
1019
+ phase: 'ai-reviewing',
1020
+ workflow,
1021
+ });
1022
+ const { passed } = await this.performAICodeReview(workflowId, execId);
1023
+ const latest = this.workflows.get(workflowId);
1024
+ if (latest) {
1025
+ const timeline = (latest.timeline || []);
1026
+ const item = timeline.find((t) => t.executionId === execId);
1027
+ if (item) {
1028
+ item.endTime = new Date().toISOString();
1029
+ if (!passed) {
1030
+ item.reason = latest.error || 'AI review did not pass the quality threshold';
1031
+ }
1032
+ latest.timeline = this.computeVisibleTimeline(latest);
1033
+ await this.saveWorkflow(latest);
1034
+ }
1022
1035
  }
1023
1036
  if (!passed) {
1024
1037
  const reason = workflow.error || 'AI review did not pass the quality threshold';
@@ -1033,10 +1046,10 @@ export class VibingOrchestrator extends EventEmitter {
1033
1046
  workflow.lastUpdatedAt = new Date().toISOString();
1034
1047
  workflow.timeline = this.computeVisibleTimeline(workflow);
1035
1048
  await this.saveWorkflow(workflow);
1036
- await this.transitionToPhase(workflowId, 'awaiting-review');
1037
1049
  workflow.status = 'awaiting-review';
1038
1050
  workflow.lastUpdatedAt = new Date().toISOString();
1039
1051
  await this.saveWorkflow(workflow);
1052
+ await this.executeAwaitingReview(workflowId);
1040
1053
  const wf = this.workflows.get(workflowId);
1041
1054
  if (wf?.metadata.stepByStepMode) {
1042
1055
  log.info('Manual step mode – AI review passed; waiting for Continue', { workflowId }, 'vibing-orchestrator');
@@ -1046,6 +1059,25 @@ export class VibingOrchestrator extends EventEmitter {
1046
1059
  await this.handlePhaseFailure(workflowId, 'ai-reviewing', error);
1047
1060
  }
1048
1061
  }
1062
+ async executeAwaitingReview(workflowId) {
1063
+ const workflow = this.workflows.get(workflowId);
1064
+ if (!workflow)
1065
+ return;
1066
+ await this.transitionToPhase(workflowId, 'awaiting-review');
1067
+ const hasReal = Array.isArray(workflow.timeline)
1068
+ ? workflow.timeline.some((t) => t.phase === 'awaiting-review' && !t.placeholder)
1069
+ : false;
1070
+ if (!hasReal) {
1071
+ workflow.timeline = this.buildTimeline(workflow, {
1072
+ id: `awaiting-review:${new Date().toISOString()}`,
1073
+ label: PHASE_LABELS['awaiting-review'],
1074
+ phase: 'awaiting-review',
1075
+ startTime: new Date().toISOString(),
1076
+ });
1077
+ }
1078
+ workflow.timeline = this.computeVisibleTimeline(workflow);
1079
+ await this.saveWorkflow(workflow);
1080
+ }
1049
1081
  /**
1050
1082
  * Execute validation phase with quality checks
1051
1083
  */
@@ -1056,51 +1088,10 @@ export class VibingOrchestrator extends EventEmitter {
1056
1088
  await this.transitionToPhase(workflowId, 'validating');
1057
1089
  try {
1058
1090
  if (!workflow.metadata.autoQualityChecks) {
1059
- log.info('Skipping quality checks (disabled)', { taskId: workflow.taskId }, 'vibing-orchestrator');
1060
- // Create a generic execution for consistency/observability
1061
- const worktree = this.agentService.getWorktreeInfo(workflow.taskId);
1062
- const workingDirectory = worktree?.path || process.cwd();
1063
- const execId = await this.agentService.startGenericExecution(workflow.taskId, {
1064
- workflowId,
1065
- workingDirectory,
1066
- });
1067
- // Track execution id for validating phase and timeline
1068
- workflow.executionIds = workflow.executionIds || {};
1069
- const list = workflow.executionIds['validating'] || [];
1070
- workflow.executionIds['validating'] = [...list, execId];
1071
- workflow.timeline = this.buildTimeline(workflow, {
1072
- id: execId,
1073
- label: list.length >= 1 ? `Validation – Retry #${list.length + 1}` : 'Validation',
1074
- phase: 'validating',
1075
- attempt: list.length + 1,
1076
- executionId: execId,
1077
- startTime: new Date().toISOString(),
1078
- endTime: new Date().toISOString(),
1079
- reason: 'Auto quality checks disabled; skipping validation',
1080
- });
1081
- await this.agentService.logGenericExecution(execId, '[validation] Auto quality checks disabled; skipping validation.');
1082
- await this.agentService.completeGenericExecution(execId, 'completed');
1083
- workflow.timeline = this.computeVisibleTimeline(workflow);
1084
- await this.saveWorkflow(workflow);
1085
- // Mark checkpoint as validated even when skipping checks
1086
- workflow.status = 'validated';
1087
- workflow.lastUpdatedAt = new Date().toISOString();
1088
- await this.saveWorkflow(workflow);
1089
- await this.transitionToPhase(workflowId, 'awaiting-review');
1090
- const wf = this.workflows.get(workflowId);
1091
- if (wf?.metadata.stepByStepMode) {
1092
- log.info('Manual step mode – validation skipped; waiting for Continue (awaiting-review)', { workflowId }, 'vibing-orchestrator');
1093
- }
1091
+ // TODO implement later
1094
1092
  return;
1095
1093
  }
1096
1094
  log.info('Running quality checks', { taskId: workflow.taskId }, 'vibing-orchestrator');
1097
- // Timeline: start validation
1098
- workflow.timeline = this.buildTimeline(workflow, {
1099
- id: `validation:${new Date().toISOString()}`,
1100
- label: 'Validation',
1101
- phase: 'validating',
1102
- startTime: new Date().toISOString(),
1103
- });
1104
1095
  // Get worktree path for quality checks
1105
1096
  const worktree = this.agentService.getWorktreeInfo(workflow.taskId);
1106
1097
  const workingDirectory = worktree?.path || process.cwd();
@@ -1109,20 +1100,27 @@ export class VibingOrchestrator extends EventEmitter {
1109
1100
  workflowId,
1110
1101
  workingDirectory,
1111
1102
  });
1103
+ const qualityOverride = workflow.metadata?.aiRoutingOverrides?.quality_checks;
1104
+ try {
1105
+ const resolved = await this.agentService.previewProviderForOperation('quality_checks', qualityOverride);
1106
+ const providerLabel = resolved.model
1107
+ ? `${resolved.provider} (model: ${resolved.model})`
1108
+ : resolved.provider;
1109
+ await this.agentService.logGenericExecution(execId, `[validation] Using provider: ${providerLabel}`);
1110
+ }
1111
+ catch (err) {
1112
+ log.warn('Failed to resolve quality-check provider', err, 'vibing-orchestrator');
1113
+ }
1112
1114
  workflow.executionIds = workflow.executionIds || {};
1113
1115
  const valList = workflow.executionIds['validating'] || [];
1114
1116
  workflow.executionIds['validating'] = [...valList, execId];
1115
- // Tie timeline item to this execution id
1116
- {
1117
- const timeline = workflow.timeline;
1118
- const tIdx = timeline.length - 1;
1119
- if (tIdx >= 0) {
1120
- const t = timeline[tIdx];
1121
- if (t && t.phase === 'validating' && !t.executionId)
1122
- t.executionId = execId;
1123
- }
1124
- }
1125
- workflow.timeline = this.computeVisibleTimeline(workflow);
1117
+ workflow.timeline = this.buildTimeline(workflow, {
1118
+ id: `validation:${new Date().toISOString()}`,
1119
+ label: 'Validation',
1120
+ phase: 'validating',
1121
+ startTime: new Date().toISOString(),
1122
+ executionId: execId,
1123
+ });
1126
1124
  await this.saveWorkflow(workflow);
1127
1125
  // Notify listeners that a validation execution has started
1128
1126
  this.emit('workflowExecutionStarted', {
@@ -1158,14 +1156,12 @@ export class VibingOrchestrator extends EventEmitter {
1158
1156
  });
1159
1157
  workflow.qualityResults = qualityResults;
1160
1158
  // Timeline: end validation
1161
- {
1162
- const timeline = workflow.timeline;
1163
- for (let i = timeline.length - 1; i >= 0; i--) {
1164
- const t = timeline[i];
1165
- if (t.phase === 'validating' && !t.endTime) {
1166
- t.endTime = new Date().toISOString();
1167
- break;
1168
- }
1159
+ const timeline = workflow.timeline;
1160
+ for (let i = timeline.length - 1; i >= 0; i--) {
1161
+ const t = timeline[i];
1162
+ if (t.phase === 'validating' && !t.endTime) {
1163
+ t.endTime = new Date().toISOString();
1164
+ break;
1169
1165
  }
1170
1166
  }
1171
1167
  // Mark execution complete (completed even if checks failed; workflow handles failure)
@@ -1184,37 +1180,17 @@ export class VibingOrchestrator extends EventEmitter {
1184
1180
  }
1185
1181
  log.info('Quality checks passed', { taskId: workflow.taskId }, 'vibing-orchestrator');
1186
1182
  // Update checkpoint status
1183
+ await this.transitionToPhase(workflowId, 'validated');
1187
1184
  workflow.status = 'validated';
1188
1185
  workflow.lastUpdatedAt = new Date().toISOString();
1189
1186
  await this.saveWorkflow(workflow);
1190
- // Move to AI review phase or directly to reviewing
1191
1187
  if (workflow.metadata.aiCodeReview) {
1192
- await this.transitionToPhase(workflowId, 'ai-reviewing');
1193
1188
  if (workflow.metadata.stepByStepMode) {
1194
1189
  log.info('Manual step mode – waiting for Continue to run AI review', { workflowId }, 'vibing-orchestrator');
1195
1190
  }
1196
1191
  else {
1197
1192
  await this.executeAiReviewPhase(workflowId);
1198
1193
  }
1199
- return;
1200
- }
1201
- await this.transitionToPhase(workflowId, 'validated');
1202
- // If manual step mode is off and human approval not required, auto-approve
1203
- if (!workflow.metadata.stepByStepMode && !workflow.metadata.requireHumanApproval) {
1204
- log.info('Auto-approving workflow (human approval disabled)', { workflowId }, 'vibing-orchestrator');
1205
- // Update checkpoint to approved
1206
- workflow.status = 'approved';
1207
- workflow.lastUpdatedAt = new Date().toISOString();
1208
- await this.saveWorkflow(workflow);
1209
- await this.approveWorkflow(workflowId);
1210
- }
1211
- else {
1212
- // Move into awaiting-review phase so UI shows review actions
1213
- await this.transitionToPhase(workflowId, 'awaiting-review');
1214
- workflow.status = 'awaiting-review';
1215
- workflow.lastUpdatedAt = new Date().toISOString();
1216
- await this.saveWorkflow(workflow);
1217
- this.emit('workflowReadyForReview', workflow);
1218
1194
  }
1219
1195
  }
1220
1196
  catch (error) {
@@ -1238,57 +1214,102 @@ export class VibingOrchestrator extends EventEmitter {
1238
1214
  if (!workflow)
1239
1215
  return;
1240
1216
  await this.transitionToPhase(workflowId, 'merging');
1217
+ let executionId;
1241
1218
  try {
1242
1219
  log.info('AI merging changes', { taskId: workflow.taskId }, 'vibing-orchestrator');
1243
- // AI-assisted merge to avoid raw git conflicts
1244
- const { executionId } = await this.agentService.aiMerge(workflow.taskId, { baseBranch: 'main' }, providerOverride || workflow.metadata?.aiRoutingOverrides?.ai_merge);
1220
+ const mergeList = workflow.executionIds?.['merging'] || [];
1221
+ const attempt = mergeList.length + 1;
1222
+ executionId = generateId('merge');
1223
+ const startTime = new Date().toISOString();
1245
1224
  workflow.executionIds = workflow.executionIds || {};
1246
- const mergeList = workflow.executionIds['merging'] || [];
1247
1225
  workflow.executionIds['merging'] = [...mergeList, executionId];
1248
- // Timeline: record merge attempt start
1249
1226
  workflow.timeline = this.buildTimeline(workflow, {
1250
1227
  id: executionId,
1251
- label: mergeList.length >= 1 ? `Merge – Retry #${mergeList.length + 1}` : 'Merge',
1228
+ label: attempt > 1 ? `Merge – Retry #${attempt}` : 'Merge',
1252
1229
  phase: 'merging',
1253
- attempt: mergeList.length + 1,
1230
+ attempt,
1254
1231
  executionId,
1255
- startTime: new Date().toISOString(),
1232
+ startTime,
1256
1233
  });
1234
+ workflow.lastUpdatedAt = startTime;
1257
1235
  await this.saveWorkflow(workflow);
1258
- // Notify listeners that a merge execution for this workflow has started
1236
+ this.emit('workflowUpdated', workflow);
1259
1237
  this.emit('workflowExecutionStarted', {
1260
1238
  workflowId,
1261
1239
  executionId,
1262
1240
  phase: 'merging',
1263
1241
  workflow,
1264
1242
  });
1265
- // Wait for AI merge to complete
1266
- await this.waitForExecution(executionId, 15 * 60 * 1000);
1243
+ await this.agentService.aiMerge(workflow.taskId, {
1244
+ baseBranch: 'main',
1245
+ executionId,
1246
+ workflowId,
1247
+ overrides: providerOverride || workflow.metadata?.aiRoutingOverrides?.ai_merge,
1248
+ });
1267
1249
  const mergeExec = this.agentService.getExecutionStatus(executionId);
1268
1250
  if (!mergeExec || mergeExec.status !== 'completed') {
1269
1251
  throw new Error(`AI merge failed: ${mergeExec?.error || 'Unknown error'}`);
1270
1252
  }
1271
- // Timeline: complete merge attempt
1272
- const tMerge = workflow.timeline.find((t) => t.executionId === executionId);
1253
+ const latest = this.workflows.get(workflowId) || workflow;
1254
+ const timeline = (latest.timeline || []);
1255
+ const tMerge = timeline.find((t) => t.executionId === executionId);
1273
1256
  if (tMerge) {
1274
1257
  tMerge.endTime = mergeExec.endTime || new Date().toISOString();
1275
1258
  tMerge.usage = mergeExec.usage;
1276
- workflow.timeline = this.computeVisibleTimeline(workflow);
1277
- await this.saveWorkflow(workflow);
1259
+ latest.timeline = this.computeVisibleTimeline(latest);
1260
+ latest.lastUpdatedAt = new Date().toISOString();
1261
+ await this.saveWorkflow(latest);
1278
1262
  }
1279
- // Update task status to done (after merge)
1280
1263
  await this.taskService.updateTask(workflow.taskId, { status: 'done' });
1281
- // Move to cleanup phase; final completion is after cleanup
1282
1264
  await this.transitionToPhase(workflowId, 'merged');
1283
- workflow.status = 'merged';
1284
- workflow.lastUpdatedAt = new Date().toISOString();
1285
- await this.saveWorkflow(workflow);
1265
+ const mergedWorkflow = this.workflows.get(workflowId);
1266
+ if (mergedWorkflow) {
1267
+ mergedWorkflow.status = 'merged';
1268
+ mergedWorkflow.lastUpdatedAt = new Date().toISOString();
1269
+ await this.saveWorkflow(mergedWorkflow);
1270
+ }
1271
+ if (workflow.metadata.stepByStepMode) {
1272
+ log.info('Manual step mode – merge completed; waiting for Continue', { workflowId }, 'vibing-orchestrator');
1273
+ }
1274
+ else {
1275
+ await this.updateCleanupStatus(workflowId);
1276
+ }
1286
1277
  log.info('Merge completed, ready for cleanup', { taskId: workflow.taskId }, 'vibing-orchestrator');
1287
1278
  }
1288
1279
  catch (error) {
1280
+ const latest = this.workflows.get(workflowId) || workflow;
1281
+ if (executionId && latest) {
1282
+ const timeline = (latest.timeline || []);
1283
+ const pending = timeline.find((t) => t.executionId === executionId);
1284
+ if (pending && !pending.endTime) {
1285
+ pending.endTime = new Date().toISOString();
1286
+ if (!pending.reason) {
1287
+ pending.reason = error instanceof Error ? error.message : String(error);
1288
+ }
1289
+ latest.timeline = this.computeVisibleTimeline(latest);
1290
+ latest.lastUpdatedAt = new Date().toISOString();
1291
+ await this.saveWorkflow(latest);
1292
+ }
1293
+ }
1289
1294
  await this.handlePhaseFailure(workflowId, 'merging', error);
1290
1295
  }
1291
1296
  }
1297
+ async updateCleanupStatus(workflowId) {
1298
+ const workflow = this.workflows.get(workflowId);
1299
+ if (!workflow)
1300
+ return;
1301
+ await this.transitionToPhase(workflowId, 'cleaning');
1302
+ workflow.status = 'cleaning';
1303
+ workflow.lastUpdatedAt = new Date().toISOString();
1304
+ workflow.timeline = this.buildTimeline(workflow, {
1305
+ id: `cleaning:${new Date().toISOString()}`,
1306
+ label: 'Cleaning',
1307
+ phase: 'cleaning',
1308
+ startTime: new Date().toISOString(),
1309
+ });
1310
+ await this.saveWorkflow(workflow);
1311
+ this.emit('workflowUpdated', workflow);
1312
+ }
1292
1313
  /**
1293
1314
  * Public: run merge phase on demand
1294
1315
  * This will attempt merge regardless of current phase.
@@ -1297,10 +1318,7 @@ export class VibingOrchestrator extends EventEmitter {
1297
1318
  const workflow = this.workflows.get(workflowId);
1298
1319
  if (!workflow)
1299
1320
  throw new Error(`Workflow ${workflowId} not found`);
1300
- await this.transitionToPhase(workflowId, 'merging');
1301
- // If not already approved/reviewing, proceed anyway to merge attempt
1302
- await this.executeMerge(workflowId, options);
1303
- await this.transitionToPhase(workflowId, 'merged');
1321
+ await this.executeMerge(workflowId, { provider: options?.provider, model: options?.model });
1304
1322
  }
1305
1323
  /**
1306
1324
  * Execute cleanup phase: remove worktree/branch and finalize workflow
@@ -1434,11 +1452,18 @@ export class VibingOrchestrator extends EventEmitter {
1434
1452
  */
1435
1453
  async waitForExecution(executionId, timeoutMs = 30 * 60 * 1000) {
1436
1454
  const startTime = Date.now();
1455
+ let missingStatusWarned = false;
1437
1456
  while (Date.now() - startTime < timeoutMs) {
1438
1457
  const execution = this.agentService.getExecutionStatus(executionId);
1439
1458
  if (!execution) {
1440
- throw new Error(`Execution ${executionId} not found`);
1459
+ if (!missingStatusWarned) {
1460
+ log.debug('Execution status not yet available; waiting for agent to register', { executionId }, 'vibing-orchestrator');
1461
+ missingStatusWarned = true;
1462
+ }
1463
+ await new Promise((resolve) => setTimeout(resolve, 500));
1464
+ continue;
1441
1465
  }
1466
+ missingStatusWarned = false;
1442
1467
  if (['completed', 'failed', 'cancelled'].includes(execution.status)) {
1443
1468
  return;
1444
1469
  }
@@ -1623,7 +1648,6 @@ export class VibingOrchestrator extends EventEmitter {
1623
1648
  await this.rerunImplementation(workflowId);
1624
1649
  }
1625
1650
  else {
1626
- await this.transitionToPhase(workflowId, 'implementing');
1627
1651
  await this.executeImplementation(workflowId);
1628
1652
  }
1629
1653
  return;
@@ -1636,23 +1660,21 @@ export class VibingOrchestrator extends EventEmitter {
1636
1660
  }
1637
1661
  switch (next) {
1638
1662
  case 'implementing':
1639
- await this.transitionToPhase(workflowId, 'implementing');
1640
1663
  await this.executeImplementation(workflowId);
1641
1664
  return;
1642
1665
  case 'validating':
1643
- await this.transitionToPhase(workflowId, 'validating');
1644
1666
  await this.executeValidation(workflowId);
1645
1667
  return;
1646
1668
  case 'ai-reviewing':
1647
- await this.transitionToPhase(workflowId, 'ai-reviewing');
1648
1669
  await this.executeAiReviewPhase(workflowId);
1649
1670
  return;
1671
+ case 'awaiting-review':
1672
+ await this.executeAwaitingReview(workflowId);
1673
+ return;
1650
1674
  case 'merging':
1651
- await this.transitionToPhase(workflowId, 'merging');
1652
1675
  await this.executeMerge(workflowId);
1653
1676
  return;
1654
1677
  case 'cleaning':
1655
- await this.transitionToPhase(workflowId, 'cleaning');
1656
1678
  await this.executeCleanup(workflowId);
1657
1679
  return;
1658
1680
  default:
@@ -1832,6 +1854,14 @@ function serializeVibe(workflow) {
1832
1854
  timestamp: new Date(workflow.qualityResults.timestamp).toISOString(),
1833
1855
  }
1834
1856
  : undefined,
1857
+ aiReviewResult: workflow.aiReviewResult
1858
+ ? {
1859
+ ...workflow.aiReviewResult,
1860
+ timestamp: workflow.aiReviewResult.timestamp
1861
+ ? new Date(workflow.aiReviewResult.timestamp).toISOString()
1862
+ : undefined,
1863
+ }
1864
+ : undefined,
1835
1865
  };
1836
1866
  }
1837
1867
  async function getDbStats(db) {