@ronkovic/aad 0.4.0 → 0.5.1

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 (39) hide show
  1. package/README.md +42 -4
  2. package/package.json +1 -1
  3. package/src/__tests__/e2e/cleanup-e2e.test.ts +186 -0
  4. package/src/__tests__/e2e/dashboard-api-e2e.test.ts +87 -0
  5. package/src/__tests__/e2e/pipeline-e2e.test.ts +10 -69
  6. package/src/__tests__/e2e/resume-e2e.test.ts +7 -11
  7. package/src/__tests__/e2e/retry-e2e.test.ts +285 -0
  8. package/src/__tests__/e2e/status-e2e.test.ts +227 -0
  9. package/src/__tests__/e2e/tdd-pipeline-e2e.test.ts +360 -0
  10. package/src/__tests__/helpers/index.ts +6 -0
  11. package/src/__tests__/helpers/mock-claude-provider.ts +53 -0
  12. package/src/__tests__/helpers/mock-logger.ts +36 -0
  13. package/src/__tests__/helpers/wait-helpers.ts +34 -0
  14. package/src/__tests__/integration/pipeline.test.ts +2 -0
  15. package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +79 -0
  16. package/src/modules/claude-provider/__tests__/provider-registry.test.ts +2 -0
  17. package/src/modules/cli/__tests__/cleanup.test.ts +1 -0
  18. package/src/modules/cli/__tests__/resume.test.ts +3 -0
  19. package/src/modules/cli/__tests__/run.test.ts +36 -0
  20. package/src/modules/cli/__tests__/status.test.ts +1 -0
  21. package/src/modules/cli/app.ts +2 -0
  22. package/src/modules/cli/commands/resume.ts +11 -6
  23. package/src/modules/cli/commands/run.ts +14 -2
  24. package/src/modules/dashboard/ui/dashboard.html +640 -474
  25. package/src/modules/planning/__tests__/planning-service.test.ts +2 -0
  26. package/src/modules/process-manager/__tests__/process-manager.test.ts +2 -0
  27. package/src/modules/process-manager/process-manager.ts +2 -1
  28. package/src/modules/task-execution/__tests__/executor.test.ts +420 -10
  29. package/src/modules/task-execution/executor.ts +76 -0
  30. package/src/modules/task-queue/dispatcher.ts +46 -2
  31. package/src/shared/__tests__/config.test.ts +30 -0
  32. package/src/shared/__tests__/events.test.ts +42 -16
  33. package/src/shared/__tests__/shutdown-handler.test.ts +96 -0
  34. package/src/shared/config.ts +4 -0
  35. package/src/shared/events.ts +5 -0
  36. package/src/shared/memory-check.ts +2 -2
  37. package/src/shared/shutdown-handler.ts +12 -5
  38. package/src/shared/types.ts +12 -0
  39. package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +0 -127
@@ -55,6 +55,8 @@ function createMockConfig(teamsEnabled: boolean = false): Config {
55
55
  host: "localhost",
56
56
  },
57
57
  git: { autoPush: false },
58
+ skipCompleted: true,
59
+ strictTdd: false,
58
60
  };
59
61
  }
60
62
 
@@ -25,6 +25,8 @@ describe("ProcessManager", () => {
25
25
  memorySync: false,
26
26
  dashboard: { enabled: false, port: 7333, host: "localhost" },
27
27
  git: { autoPush: false },
28
+ skipCompleted: true,
29
+ strictTdd: false,
28
30
  },
29
31
  logger,
30
32
  });
@@ -29,7 +29,7 @@ export class ProcessManager {
29
29
  /**
30
30
  * Initialize worker pool
31
31
  */
32
- async initializePool(numWorkers: number): Promise<void> {
32
+ initializePool(numWorkers: number): Promise<void> {
33
33
  if (this.initialized) {
34
34
  throw new ProcessManagerError("ProcessManager already initialized");
35
35
  }
@@ -49,6 +49,7 @@ export class ProcessManager {
49
49
 
50
50
  this.initialized = true;
51
51
  this.deps.logger.info({ numWorkers }, "Worker pool initialized");
52
+ return Promise.resolve();
52
53
  }
53
54
 
54
55
  /**
@@ -43,7 +43,9 @@ describe("executeTddPipeline", () => {
43
43
  teams: { splitter: false, reviewer: false },
44
44
  memorySync: true,
45
45
  dashboard: { enabled: true, port: 7333, host: "localhost" },
46
- git: { autoPush: false },
46
+ git: { autoPush: false },
47
+ skipCompleted: true,
48
+ strictTdd: true,
47
49
  };
48
50
 
49
51
  const mockProvider: ClaudeProvider = {
@@ -140,7 +142,9 @@ describe("executeTddPipeline", () => {
140
142
  teams: { splitter: false, reviewer: false },
141
143
  memorySync: true,
142
144
  dashboard: { enabled: true, port: 7333, host: "localhost" },
143
- git: { autoPush: false },
145
+ git: { autoPush: false },
146
+ skipCompleted: true,
147
+ strictTdd: true,
144
148
  };
145
149
 
146
150
  let callCount = 0;
@@ -227,7 +231,9 @@ describe("executeTddPipeline", () => {
227
231
  teams: { splitter: false, reviewer: false },
228
232
  memorySync: true,
229
233
  dashboard: { enabled: true, port: 7333, host: "localhost" },
230
- git: { autoPush: false },
234
+ git: { autoPush: false },
235
+ skipCompleted: true,
236
+ strictTdd: true,
231
237
  };
232
238
 
233
239
  const mockProvider: ClaudeProvider = {
@@ -318,7 +324,9 @@ describe("executeTddPipeline", () => {
318
324
  teams: { splitter: false, reviewer: false },
319
325
  memorySync: true,
320
326
  dashboard: { enabled: true, port: 7333, host: "localhost" },
321
- git: { autoPush: false },
327
+ git: { autoPush: false },
328
+ skipCompleted: true,
329
+ strictTdd: true,
322
330
  };
323
331
 
324
332
  let phaseCount = 0;
@@ -420,7 +428,9 @@ describe("executeTddPipeline", () => {
420
428
  teams: { splitter: false, reviewer: false },
421
429
  memorySync: true,
422
430
  dashboard: { enabled: true, port: 7333, host: "localhost" },
423
- git: { autoPush: false },
431
+ git: { autoPush: false },
432
+ skipCompleted: true,
433
+ strictTdd: true,
424
434
  };
425
435
 
426
436
  const mockProvider: ClaudeProvider = {
@@ -517,7 +527,9 @@ describe("executeTddPipeline", () => {
517
527
  teams: { splitter: false, reviewer: false },
518
528
  memorySync: true,
519
529
  dashboard: { enabled: true, port: 7333, host: "localhost" },
520
- git: { autoPush: false },
530
+ git: { autoPush: false },
531
+ skipCompleted: true,
532
+ strictTdd: true,
521
533
  };
522
534
 
523
535
  let callCount = 0;
@@ -578,7 +590,9 @@ describe("executeTddPipeline", () => {
578
590
  teams: { splitter: false, reviewer: false },
579
591
  memorySync: true,
580
592
  dashboard: { enabled: true, port: 7333, host: "localhost" },
581
- git: { autoPush: false },
593
+ git: { autoPush: false },
594
+ skipCompleted: true,
595
+ strictTdd: true,
582
596
  };
583
597
 
584
598
  const mockProvider: ClaudeProvider = {
@@ -636,7 +650,9 @@ describe("executeTddPipeline", () => {
636
650
  teams: { splitter: false, reviewer: false },
637
651
  memorySync: true,
638
652
  dashboard: { enabled: true, port: 7333, host: "localhost" },
639
- git: { autoPush: false },
653
+ git: { autoPush: false },
654
+ skipCompleted: true,
655
+ strictTdd: true,
640
656
  };
641
657
 
642
658
  const mockProvider: ClaudeProvider = {
@@ -695,7 +711,9 @@ describe("executeTddPipeline", () => {
695
711
  teams: { splitter: false, reviewer: false },
696
712
  memorySync: true,
697
713
  dashboard: { enabled: true, port: 7333, host: "localhost" },
698
- git: { autoPush: false },
714
+ git: { autoPush: false },
715
+ skipCompleted: true,
716
+ strictTdd: true,
699
717
  };
700
718
 
701
719
  const mockProvider: ClaudeProvider = {
@@ -747,7 +765,9 @@ describe("executeTddPipeline", () => {
747
765
  teams: { splitter: false, reviewer: false },
748
766
  memorySync: true,
749
767
  dashboard: { enabled: true, port: 7333, host: "localhost" },
750
- git: { autoPush: false },
768
+ git: { autoPush: false },
769
+ skipCompleted: true,
770
+ strictTdd: true,
751
771
  };
752
772
 
753
773
  const mockProvider: ClaudeProvider = {
@@ -800,6 +820,8 @@ describe("executeTddPipeline", () => {
800
820
  memorySync: true,
801
821
  dashboard: { enabled: true, port: 7333, host: "localhost" },
802
822
  git: { autoPush: false },
823
+ skipCompleted: true,
824
+ strictTdd: true,
803
825
  };
804
826
 
805
827
  const mockProvider: ClaudeProvider = {
@@ -843,4 +865,392 @@ describe("executeTddPipeline", () => {
843
865
  expect(warnEvents.some((e) => e.entry?.message?.includes("Red phase"))).toBe(true);
844
866
  expect(warnEvents.some((e) => e.entry?.message?.includes("Green phase"))).toBe(true);
845
867
  });
868
+
869
+ test("skips pipeline when tests already pass (pre-check succeeds)", async () => {
870
+ const task: Task = {
871
+ taskId: createTaskId("task-skip-001"),
872
+ title: "Already passing task",
873
+ description: "Tests already pass",
874
+ filesToModify: ["src/feature.ts"],
875
+ dependsOn: [],
876
+ priority: 1,
877
+ status: "running",
878
+ retryCount: 0,
879
+ };
880
+
881
+ const workspace: WorkspaceInfo = {
882
+ path: "/workspace",
883
+ language: "typescript",
884
+ packageManager: "bun",
885
+ framework: "hono",
886
+ testFramework: "bun-test",
887
+ };
888
+
889
+ const config: Config = {
890
+ workers: { num: 2, max: 8 },
891
+ models: {},
892
+ timeouts: { claude: 1200, test: 600, staleTask: 5400 },
893
+ retry: { maxRetries: 2 },
894
+ debug: false,
895
+ adaptiveEffort: false,
896
+ teams: { splitter: false, reviewer: false },
897
+ memorySync: true,
898
+ dashboard: { enabled: true, port: 7333, host: "localhost" },
899
+ git: { autoPush: false },
900
+ skipCompleted: true,
901
+ strictTdd: false,
902
+ };
903
+
904
+ let claudeCallCount = 0;
905
+ const mockProvider: ClaudeProvider = {
906
+ async call(): Promise<ClaudeResponse> {
907
+ claudeCallCount++;
908
+ return {
909
+ result: "Should not be called",
910
+ exitCode: 0,
911
+ model: "claude-sonnet-4-5",
912
+ effortLevel: "medium",
913
+ duration: 1000,
914
+ };
915
+ },
916
+ };
917
+
918
+ const mockMergeService = {
919
+ async mergeToParent(): Promise<MergeResult> {
920
+ throw new Error("Should not reach merge");
921
+ },
922
+ } as unknown as MergeService;
923
+
924
+ const events: string[] = [];
925
+ const mockEventBus = {
926
+ on() {},
927
+ off() {},
928
+ emit(event: any) {
929
+ events.push(event.type);
930
+ },
931
+ } as unknown as EventBus;
932
+
933
+ // Pre-check always passes
934
+ const mockSpawner = {
935
+ async spawn() {
936
+ return { exitCode: 0, stdout: "All tests passed", stderr: "" };
937
+ },
938
+ };
939
+
940
+ const result = await executeTddPipeline(
941
+ task, workspace, "branch", "main", "/parent", createRunId("run-skip"), config,
942
+ mockProvider, mockMergeService, mockEventBus, mockSpawner
943
+ );
944
+
945
+ expect(result.status).toBe("completed");
946
+ expect(result.skipped).toBe(true);
947
+ expect(result.phasesExecuted).toEqual(["pre-check"]);
948
+ expect(claudeCallCount).toBe(0); // No Claude calls made
949
+ expect(events).toContain("execution:skipped");
950
+ });
951
+
952
+ test("runs full pipeline when tests fail pre-check", async () => {
953
+ const task: Task = {
954
+ taskId: createTaskId("task-run-full"),
955
+ title: "Tests need work",
956
+ description: "Pre-check fails, run full pipeline",
957
+ filesToModify: [],
958
+ dependsOn: [],
959
+ priority: 1,
960
+ status: "running",
961
+ retryCount: 0,
962
+ };
963
+
964
+ const workspace: WorkspaceInfo = {
965
+ path: "/workspace",
966
+ language: "typescript",
967
+ packageManager: "bun",
968
+ framework: "hono",
969
+ testFramework: "bun-test",
970
+ };
971
+
972
+ const config: Config = {
973
+ workers: { num: 2, max: 8 },
974
+ models: {},
975
+ timeouts: { claude: 1200, test: 600, staleTask: 5400 },
976
+ retry: { maxRetries: 2 },
977
+ debug: false,
978
+ adaptiveEffort: false,
979
+ teams: { splitter: false, reviewer: false },
980
+ memorySync: true,
981
+ dashboard: { enabled: true, port: 7333, host: "localhost" },
982
+ git: { autoPush: false },
983
+ skipCompleted: true,
984
+ strictTdd: false,
985
+ };
986
+
987
+ const mockProvider: ClaudeProvider = {
988
+ async call(): Promise<ClaudeResponse> {
989
+ return {
990
+ result: "OK",
991
+ exitCode: 0,
992
+ model: "claude-sonnet-4-5",
993
+ effortLevel: "medium",
994
+ duration: 1000,
995
+ };
996
+ },
997
+ };
998
+
999
+ const mockMergeService = {
1000
+ async mergeToParent(): Promise<MergeResult> {
1001
+ return { success: true };
1002
+ },
1003
+ } as unknown as MergeService;
1004
+
1005
+ const events: string[] = [];
1006
+ const mockEventBus = {
1007
+ on() {},
1008
+ off() {},
1009
+ emit(event: any) {
1010
+ events.push(event.type);
1011
+ },
1012
+ } as unknown as EventBus;
1013
+
1014
+ let precheckDone = false;
1015
+ const mockSpawner = {
1016
+ async spawn() {
1017
+ if (!precheckDone) {
1018
+ precheckDone = true;
1019
+ return { exitCode: 1, stdout: "", stderr: "Tests failed" };
1020
+ }
1021
+ return { exitCode: 0, stdout: "OK", stderr: "" };
1022
+ },
1023
+ };
1024
+
1025
+ const result = await executeTddPipeline(
1026
+ task, workspace, "branch", "main", "/parent", createRunId("run-full"), config,
1027
+ mockProvider, mockMergeService, mockEventBus, mockSpawner
1028
+ );
1029
+
1030
+ expect(result.status).toBe("completed");
1031
+ expect(result.skipped).toBeUndefined();
1032
+ expect(events).toContain("execution:phase:started");
1033
+ expect(events).not.toContain("execution:skipped");
1034
+ });
1035
+
1036
+ test("respects strictTdd flag to skip pre-check", async () => {
1037
+ const task: Task = {
1038
+ taskId: createTaskId("task-strict"),
1039
+ title: "Strict TDD mode",
1040
+ description: "Always run full pipeline",
1041
+ filesToModify: [],
1042
+ dependsOn: [],
1043
+ priority: 1,
1044
+ status: "running",
1045
+ retryCount: 0,
1046
+ };
1047
+
1048
+ const workspace: WorkspaceInfo = {
1049
+ path: "/workspace",
1050
+ language: "typescript",
1051
+ packageManager: "bun",
1052
+ framework: "hono",
1053
+ testFramework: "bun-test",
1054
+ };
1055
+
1056
+ const config: Config = {
1057
+ workers: { num: 2, max: 8 },
1058
+ models: {},
1059
+ timeouts: { claude: 1200, test: 600, staleTask: 5400 },
1060
+ retry: { maxRetries: 2 },
1061
+ debug: false,
1062
+ adaptiveEffort: false,
1063
+ teams: { splitter: false, reviewer: false },
1064
+ memorySync: true,
1065
+ dashboard: { enabled: true, port: 7333, host: "localhost" },
1066
+ git: { autoPush: false },
1067
+ skipCompleted: true,
1068
+ strictTdd: true,
1069
+ };
1070
+
1071
+ const mockProvider: ClaudeProvider = {
1072
+ async call(): Promise<ClaudeResponse> {
1073
+ return {
1074
+ result: "OK",
1075
+ exitCode: 0,
1076
+ model: "claude-sonnet-4-5",
1077
+ effortLevel: "medium",
1078
+ duration: 1000,
1079
+ };
1080
+ },
1081
+ };
1082
+
1083
+ const mockMergeService = {
1084
+ async mergeToParent(): Promise<MergeResult> {
1085
+ return { success: true };
1086
+ },
1087
+ } as unknown as MergeService;
1088
+
1089
+ const events: string[] = [];
1090
+ const mockEventBus = {
1091
+ on() {},
1092
+ off() {},
1093
+ emit(event: any) {
1094
+ events.push(event.type);
1095
+ },
1096
+ } as unknown as EventBus;
1097
+
1098
+ const mockSpawner = {
1099
+ async spawn() {
1100
+ return { exitCode: 0, stdout: "OK", stderr: "" };
1101
+ },
1102
+ };
1103
+
1104
+ const result = await executeTddPipeline(
1105
+ task, workspace, "branch", "main", "/parent", createRunId("run-strict"), config,
1106
+ mockProvider, mockMergeService, mockEventBus, mockSpawner
1107
+ );
1108
+
1109
+ expect(result.status).toBe("completed");
1110
+ expect(result.skipped).toBeUndefined();
1111
+ expect(events).toContain("execution:phase:started");
1112
+ expect(events).not.toContain("execution:skipped");
1113
+ });
1114
+
1115
+ test("emits execution:skipped event when skipping", async () => {
1116
+ const task: Task = {
1117
+ taskId: createTaskId("task-event-skip"),
1118
+ title: "Event test",
1119
+ description: "Test skip event",
1120
+ filesToModify: [],
1121
+ dependsOn: [],
1122
+ priority: 1,
1123
+ status: "running",
1124
+ retryCount: 0,
1125
+ };
1126
+
1127
+ const workspace: WorkspaceInfo = {
1128
+ path: "/workspace",
1129
+ language: "typescript",
1130
+ packageManager: "bun",
1131
+ framework: "hono",
1132
+ testFramework: "bun-test",
1133
+ };
1134
+
1135
+ const config: Config = {
1136
+ workers: { num: 2, max: 8 },
1137
+ models: {},
1138
+ timeouts: { claude: 1200, test: 600, staleTask: 5400 },
1139
+ retry: { maxRetries: 2 },
1140
+ debug: false,
1141
+ adaptiveEffort: false,
1142
+ teams: { splitter: false, reviewer: false },
1143
+ memorySync: true,
1144
+ dashboard: { enabled: true, port: 7333, host: "localhost" },
1145
+ git: { autoPush: false },
1146
+ skipCompleted: true,
1147
+ strictTdd: false,
1148
+ };
1149
+
1150
+ const mockProvider: ClaudeProvider = {
1151
+ async call(): Promise<ClaudeResponse> {
1152
+ throw new Error("Should not be called");
1153
+ },
1154
+ };
1155
+
1156
+ const mockMergeService = {
1157
+ async mergeToParent(): Promise<MergeResult> {
1158
+ throw new Error("Should not reach merge");
1159
+ },
1160
+ } as unknown as MergeService;
1161
+
1162
+ const events: Array<{ type: string; taskId?: string }> = [];
1163
+ const mockEventBus = {
1164
+ on() {},
1165
+ off() {},
1166
+ emit(event: any) {
1167
+ events.push(event);
1168
+ },
1169
+ } as unknown as EventBus;
1170
+
1171
+ const mockSpawner = {
1172
+ async spawn() {
1173
+ return { exitCode: 0, stdout: "Tests pass", stderr: "" };
1174
+ },
1175
+ };
1176
+
1177
+ await executeTddPipeline(
1178
+ task, workspace, "branch", "main", "/parent", createRunId("run-evt"), config,
1179
+ mockProvider, mockMergeService, mockEventBus, mockSpawner
1180
+ );
1181
+
1182
+ const skipEvent = events.find((e) => e.type === "execution:skipped");
1183
+ expect(skipEvent).toBeDefined();
1184
+ expect(skipEvent?.taskId).toBe(task.taskId);
1185
+ });
1186
+
1187
+ test("includes skipped flag in result when skipped", async () => {
1188
+ const task: Task = {
1189
+ taskId: createTaskId("task-result-flag"),
1190
+ title: "Result flag test",
1191
+ description: "Test result structure",
1192
+ filesToModify: [],
1193
+ dependsOn: [],
1194
+ priority: 1,
1195
+ status: "running",
1196
+ retryCount: 0,
1197
+ };
1198
+
1199
+ const workspace: WorkspaceInfo = {
1200
+ path: "/workspace",
1201
+ language: "typescript",
1202
+ packageManager: "bun",
1203
+ framework: "hono",
1204
+ testFramework: "bun-test",
1205
+ };
1206
+
1207
+ const config: Config = {
1208
+ workers: { num: 2, max: 8 },
1209
+ models: {},
1210
+ timeouts: { claude: 1200, test: 600, staleTask: 5400 },
1211
+ retry: { maxRetries: 2 },
1212
+ debug: false,
1213
+ adaptiveEffort: false,
1214
+ teams: { splitter: false, reviewer: false },
1215
+ memorySync: true,
1216
+ dashboard: { enabled: true, port: 7333, host: "localhost" },
1217
+ git: { autoPush: false },
1218
+ skipCompleted: true,
1219
+ strictTdd: false,
1220
+ };
1221
+
1222
+ const mockProvider: ClaudeProvider = {
1223
+ async call(): Promise<ClaudeResponse> {
1224
+ throw new Error("Should not be called");
1225
+ },
1226
+ };
1227
+
1228
+ const mockMergeService = {
1229
+ async mergeToParent(): Promise<MergeResult> {
1230
+ throw new Error("Should not reach merge");
1231
+ },
1232
+ } as unknown as MergeService;
1233
+
1234
+ const mockEventBus = {
1235
+ on() {},
1236
+ off() {},
1237
+ emit() {},
1238
+ } as unknown as EventBus;
1239
+
1240
+ const mockSpawner = {
1241
+ async spawn() {
1242
+ return { exitCode: 0, stdout: "Pass", stderr: "" };
1243
+ },
1244
+ };
1245
+
1246
+ const result = await executeTddPipeline(
1247
+ task, workspace, "branch", "main", "/parent", createRunId("run-flag"), config,
1248
+ mockProvider, mockMergeService, mockEventBus, mockSpawner
1249
+ );
1250
+
1251
+ expect(result.status).toBe("completed");
1252
+ expect(result.skipped).toBe(true);
1253
+ expect(result.phasesExecuted).toEqual(["pre-check"]);
1254
+ expect(result.duration).toBeGreaterThanOrEqual(0);
1255
+ });
846
1256
  });
@@ -28,9 +28,22 @@ export interface RetryContext {
28
28
  previousFailure?: PreviousFailure;
29
29
  }
30
30
 
31
+ /**
32
+ * Run pre-check to see if tests already pass
33
+ */
34
+ async function runPreCheck(
35
+ workspace: WorkspaceInfo,
36
+ testSpawner: ProcessSpawner | undefined,
37
+ timeout: number
38
+ ): Promise<boolean> {
39
+ const result = await runTests(workspace, testSpawner, timeout);
40
+ return result.success;
41
+ }
42
+
31
43
  /**
32
44
  * Execute full TDD pipeline for a task
33
45
  * Phases: Red → Green → Verify → Review → Merge
46
+ * Optionally skips pipeline if tests already pass (pre-check)
34
47
  */
35
48
  export async function executeTddPipeline(
36
49
  task: Task,
@@ -49,6 +62,68 @@ export async function executeTddPipeline(
49
62
  const startTime = Date.now();
50
63
 
51
64
  try {
65
+ // ===== Pre-check: Skip if tests already pass (unless strictTdd) =====
66
+ if (config.skipCompleted && !config.strictTdd) {
67
+ eventBus.emit({
68
+ type: "log:entry",
69
+ entry: {
70
+ level: "info",
71
+ service: "task-execution",
72
+ message: "Running pre-check to see if tests already pass",
73
+ timestamp: Date.now(),
74
+ taskId: task.taskId as string,
75
+ },
76
+ });
77
+
78
+ const testsAlreadyPass = await runPreCheck(
79
+ workspace,
80
+ testSpawner,
81
+ config.timeouts.test * 1000
82
+ );
83
+
84
+ if (testsAlreadyPass) {
85
+ const duration = Date.now() - startTime;
86
+
87
+ eventBus.emit({
88
+ type: "execution:skipped",
89
+ taskId: task.taskId,
90
+ reason: "Tests already pass",
91
+ });
92
+
93
+ eventBus.emit({
94
+ type: "log:entry",
95
+ entry: {
96
+ level: "info",
97
+ service: "task-execution",
98
+ message: "Tests already pass, skipping TDD pipeline",
99
+ timestamp: Date.now(),
100
+ taskId: task.taskId as string,
101
+ duration,
102
+ },
103
+ });
104
+
105
+ return {
106
+ taskId: task.taskId,
107
+ status: "completed",
108
+ duration,
109
+ output: "Skipped: tests already pass",
110
+ skipped: true,
111
+ phasesExecuted: ["pre-check"],
112
+ };
113
+ }
114
+
115
+ eventBus.emit({
116
+ type: "log:entry",
117
+ entry: {
118
+ level: "info",
119
+ service: "task-execution",
120
+ message: "Pre-check failed, running full TDD pipeline",
121
+ timestamp: Date.now(),
122
+ taskId: task.taskId as string,
123
+ },
124
+ });
125
+ }
126
+
52
127
  // Estimate task complexity for adaptive effort
53
128
  const complexity = estimateTaskComplexity(task);
54
129
  const testerEffort = getAdaptiveEffortLevel("tester", complexity, config);
@@ -365,6 +440,7 @@ export async function executeTddPipeline(
365
440
  status: "completed",
366
441
  duration,
367
442
  output: "TDD pipeline completed successfully",
443
+ phasesExecuted: ["red", "green", "verify", "review", "merge"],
368
444
  };
369
445
  } catch (error) {
370
446
  const duration = Date.now() - startTime;