@ronkovic/aad 0.3.9 → 0.5.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 (132) hide show
  1. package/README.md +332 -14
  2. package/package.json +6 -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 -68
  6. package/src/__tests__/e2e/resume-e2e.test.ts +9 -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 +3 -0
  15. package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +1 -1
  16. package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +6 -0
  17. package/src/modules/claude-provider/__tests__/provider-registry.test.ts +3 -0
  18. package/src/modules/cli/__tests__/cleanup.test.ts +73 -0
  19. package/src/modules/cli/__tests__/resume.test.ts +4 -0
  20. package/src/modules/cli/__tests__/run.test.ts +37 -0
  21. package/src/modules/cli/__tests__/status.test.ts +1 -0
  22. package/src/modules/cli/app.ts +2 -0
  23. package/src/modules/cli/commands/__tests__/task-dispatch-handler.test.ts +145 -0
  24. package/src/modules/cli/commands/cleanup.ts +26 -11
  25. package/src/modules/cli/commands/resume.ts +14 -8
  26. package/src/modules/cli/commands/run.ts +70 -8
  27. package/src/modules/cli/commands/task-dispatch-handler.ts +73 -3
  28. package/src/modules/dashboard/__tests__/api-graph.test.ts +332 -0
  29. package/src/modules/dashboard/__tests__/api-timeline.test.ts +461 -0
  30. package/src/modules/dashboard/routes/sse.ts +3 -2
  31. package/src/modules/dashboard/server.ts +1 -0
  32. package/src/modules/dashboard/services/sse-broadcaster.ts +29 -0
  33. package/src/modules/dashboard/ui/dashboard.html +640 -349
  34. package/src/modules/git-workspace/__tests__/branch-manager.test.ts +52 -0
  35. package/src/modules/git-workspace/__tests__/dependency-installer.test.ts +77 -0
  36. package/src/modules/git-workspace/__tests__/git-exec.test.ts +26 -0
  37. package/src/modules/git-workspace/__tests__/merge-service.test.ts +19 -0
  38. package/src/modules/git-workspace/__tests__/pr-manager.test.ts +80 -0
  39. package/src/modules/git-workspace/__tests__/template-copy.test.ts +189 -0
  40. package/src/modules/git-workspace/__tests__/worktree-cleanup.test.ts +29 -2
  41. package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +64 -4
  42. package/src/modules/git-workspace/branch-manager.ts +24 -3
  43. package/src/modules/git-workspace/dependency-installer.ts +113 -0
  44. package/src/modules/git-workspace/git-exec.ts +3 -2
  45. package/src/modules/git-workspace/index.ts +10 -1
  46. package/src/modules/git-workspace/merge-service.ts +36 -2
  47. package/src/modules/git-workspace/pr-manager.ts +278 -0
  48. package/src/modules/git-workspace/template-copy.ts +302 -0
  49. package/src/modules/git-workspace/worktree-manager.ts +37 -11
  50. package/src/modules/planning/__tests__/planning-service.test.ts +3 -0
  51. package/src/modules/planning/__tests__/planning.service.test.ts +149 -0
  52. package/src/modules/planning/__tests__/project-detection.test.ts +7 -1
  53. package/src/modules/planning/planning.service.ts +16 -2
  54. package/src/modules/planning/project-detection.ts +4 -1
  55. package/src/modules/process-manager/__tests__/process-manager.test.ts +3 -0
  56. package/src/modules/process-manager/process-manager.ts +2 -1
  57. package/src/modules/task-execution/__tests__/executor.test.ts +496 -0
  58. package/src/modules/task-execution/__tests__/tester-verify.test.ts +4 -3
  59. package/src/modules/task-execution/executor.ts +163 -4
  60. package/src/modules/task-execution/phases/implementer-green.ts +22 -5
  61. package/src/modules/task-execution/phases/merge.ts +44 -2
  62. package/src/modules/task-execution/phases/tester-red.ts +22 -5
  63. package/src/modules/task-execution/phases/tester-verify.ts +22 -6
  64. package/src/modules/task-queue/dispatcher.ts +96 -3
  65. package/src/shared/__tests__/config.test.ts +30 -0
  66. package/src/shared/__tests__/events.test.ts +42 -16
  67. package/src/shared/__tests__/prerequisites.test.ts +176 -0
  68. package/src/shared/__tests__/shutdown-handler.test.ts +96 -0
  69. package/src/shared/config.ts +10 -0
  70. package/src/shared/events.ts +5 -0
  71. package/src/shared/memory-check.ts +2 -2
  72. package/src/shared/prerequisites.ts +190 -0
  73. package/src/shared/shutdown-handler.ts +12 -5
  74. package/src/shared/types.ts +25 -0
  75. package/templates/CLAUDE.md +122 -0
  76. package/templates/settings.json +117 -0
  77. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +0 -10
  78. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +0 -10
  79. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +0 -13
  80. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +0 -10
  81. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +0 -10
  82. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +0 -5
  83. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +0 -5
  84. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +0 -10
  85. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +0 -10
  86. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +0 -13
  87. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +0 -10
  88. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +0 -10
  89. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +0 -5
  90. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +0 -5
  91. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +0 -10
  92. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +0 -10
  93. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +0 -13
  94. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +0 -10
  95. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +0 -10
  96. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +0 -5
  97. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +0 -5
  98. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +0 -10
  99. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +0 -10
  100. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +0 -13
  101. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +0 -10
  102. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +0 -10
  103. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +0 -5
  104. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +0 -5
  105. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +0 -10
  106. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +0 -10
  107. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +0 -13
  108. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +0 -10
  109. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +0 -10
  110. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +0 -5
  111. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +0 -5
  112. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +0 -10
  113. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +0 -10
  114. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +0 -13
  115. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +0 -10
  116. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +0 -10
  117. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +0 -5
  118. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +0 -5
  119. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +0 -10
  120. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +0 -10
  121. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +0 -13
  122. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +0 -10
  123. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +0 -10
  124. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +0 -5
  125. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +0 -5
  126. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +0 -10
  127. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +0 -10
  128. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +0 -13
  129. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +0 -10
  130. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +0 -10
  131. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +0 -5
  132. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +0 -5
@@ -43,6 +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 },
47
+ skipCompleted: true,
48
+ strictTdd: true,
46
49
  };
47
50
 
48
51
  const mockProvider: ClaudeProvider = {
@@ -139,6 +142,9 @@ describe("executeTddPipeline", () => {
139
142
  teams: { splitter: false, reviewer: false },
140
143
  memorySync: true,
141
144
  dashboard: { enabled: true, port: 7333, host: "localhost" },
145
+ git: { autoPush: false },
146
+ skipCompleted: true,
147
+ strictTdd: true,
142
148
  };
143
149
 
144
150
  let callCount = 0;
@@ -225,6 +231,9 @@ describe("executeTddPipeline", () => {
225
231
  teams: { splitter: false, reviewer: false },
226
232
  memorySync: true,
227
233
  dashboard: { enabled: true, port: 7333, host: "localhost" },
234
+ git: { autoPush: false },
235
+ skipCompleted: true,
236
+ strictTdd: true,
228
237
  };
229
238
 
230
239
  const mockProvider: ClaudeProvider = {
@@ -315,6 +324,9 @@ describe("executeTddPipeline", () => {
315
324
  teams: { splitter: false, reviewer: false },
316
325
  memorySync: true,
317
326
  dashboard: { enabled: true, port: 7333, host: "localhost" },
327
+ git: { autoPush: false },
328
+ skipCompleted: true,
329
+ strictTdd: true,
318
330
  };
319
331
 
320
332
  let phaseCount = 0;
@@ -416,6 +428,9 @@ describe("executeTddPipeline", () => {
416
428
  teams: { splitter: false, reviewer: false },
417
429
  memorySync: true,
418
430
  dashboard: { enabled: true, port: 7333, host: "localhost" },
431
+ git: { autoPush: false },
432
+ skipCompleted: true,
433
+ strictTdd: true,
419
434
  };
420
435
 
421
436
  const mockProvider: ClaudeProvider = {
@@ -512,6 +527,9 @@ describe("executeTddPipeline", () => {
512
527
  teams: { splitter: false, reviewer: false },
513
528
  memorySync: true,
514
529
  dashboard: { enabled: true, port: 7333, host: "localhost" },
530
+ git: { autoPush: false },
531
+ skipCompleted: true,
532
+ strictTdd: true,
515
533
  };
516
534
 
517
535
  let callCount = 0;
@@ -572,6 +590,9 @@ describe("executeTddPipeline", () => {
572
590
  teams: { splitter: false, reviewer: false },
573
591
  memorySync: true,
574
592
  dashboard: { enabled: true, port: 7333, host: "localhost" },
593
+ git: { autoPush: false },
594
+ skipCompleted: true,
595
+ strictTdd: true,
575
596
  };
576
597
 
577
598
  const mockProvider: ClaudeProvider = {
@@ -629,6 +650,9 @@ describe("executeTddPipeline", () => {
629
650
  teams: { splitter: false, reviewer: false },
630
651
  memorySync: true,
631
652
  dashboard: { enabled: true, port: 7333, host: "localhost" },
653
+ git: { autoPush: false },
654
+ skipCompleted: true,
655
+ strictTdd: true,
632
656
  };
633
657
 
634
658
  const mockProvider: ClaudeProvider = {
@@ -687,6 +711,9 @@ describe("executeTddPipeline", () => {
687
711
  teams: { splitter: false, reviewer: false },
688
712
  memorySync: true,
689
713
  dashboard: { enabled: true, port: 7333, host: "localhost" },
714
+ git: { autoPush: false },
715
+ skipCompleted: true,
716
+ strictTdd: true,
690
717
  };
691
718
 
692
719
  const mockProvider: ClaudeProvider = {
@@ -738,6 +765,9 @@ describe("executeTddPipeline", () => {
738
765
  teams: { splitter: false, reviewer: false },
739
766
  memorySync: true,
740
767
  dashboard: { enabled: true, port: 7333, host: "localhost" },
768
+ git: { autoPush: false },
769
+ skipCompleted: true,
770
+ strictTdd: true,
741
771
  };
742
772
 
743
773
  const mockProvider: ClaudeProvider = {
@@ -757,4 +787,470 @@ describe("executeTddPipeline", () => {
757
787
  expect(result.status).toBe("failed");
758
788
  expect(result.error).toBe("Unknown error");
759
789
  });
790
+
791
+ test("emits warn log events when commit fails (invalid workspace)", async () => {
792
+ const task: Task = {
793
+ taskId: createTaskId("task-commit-warn"),
794
+ title: "Commit warning test",
795
+ description: "Test warn logs on commit failure",
796
+ filesToModify: [],
797
+ dependsOn: [],
798
+ priority: 1,
799
+ status: "running",
800
+ retryCount: 0,
801
+ };
802
+
803
+ // workspace.path が無効 → git add/commit は全て失敗する想定
804
+ const workspace: WorkspaceInfo = {
805
+ path: "/nonexistent-invalid-workspace",
806
+ language: "typescript",
807
+ packageManager: "bun",
808
+ framework: "hono",
809
+ testFramework: "bun-test",
810
+ };
811
+
812
+ const config: Config = {
813
+ workers: { num: 2, max: 8 },
814
+ models: {},
815
+ timeouts: { claude: 1200, test: 600, staleTask: 5400 },
816
+ retry: { maxRetries: 2 },
817
+ debug: false,
818
+ adaptiveEffort: false,
819
+ teams: { splitter: false, reviewer: false },
820
+ memorySync: true,
821
+ dashboard: { enabled: true, port: 7333, host: "localhost" },
822
+ git: { autoPush: false },
823
+ skipCompleted: true,
824
+ strictTdd: true,
825
+ };
826
+
827
+ const mockProvider: ClaudeProvider = {
828
+ async call(): Promise<ClaudeResponse> {
829
+ return { result: "OK", exitCode: 0, model: "claude-sonnet-4-5", effortLevel: "medium", duration: 1000 };
830
+ },
831
+ };
832
+
833
+ const mockMergeService = {
834
+ async mergeToParent(): Promise<MergeResult> {
835
+ return { success: true, message: "Merged" };
836
+ },
837
+ } as unknown as MergeService;
838
+
839
+ const events: Array<{ type: string; entry?: { level?: string; message?: string } }> = [];
840
+ const mockEventBus = {
841
+ on() {},
842
+ off() {},
843
+ emit(event: any) {
844
+ events.push(event);
845
+ },
846
+ } as unknown as EventBus;
847
+
848
+ const mockSpawner = {
849
+ async spawn() {
850
+ return { exitCode: 0, stdout: "OK", stderr: "" };
851
+ },
852
+ };
853
+
854
+ await executeTddPipeline(
855
+ task, workspace, "branch", "main", "/parent", createRunId("run-cw"), config,
856
+ mockProvider, mockMergeService, mockEventBus, mockSpawner
857
+ );
858
+
859
+ const warnEvents = events.filter(
860
+ (e) => e.type === "log:entry" && e.entry?.level === "warn"
861
+ );
862
+
863
+ // Red/Green 両フェーズのコミット失敗 warn が発火
864
+ expect(warnEvents.length).toBeGreaterThanOrEqual(2);
865
+ expect(warnEvents.some((e) => e.entry?.message?.includes("Red phase"))).toBe(true);
866
+ expect(warnEvents.some((e) => e.entry?.message?.includes("Green phase"))).toBe(true);
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
+ });
760
1256
  });
@@ -200,16 +200,17 @@ describe("buildTestCommand", () => {
200
200
  expect(buildTestCommand(workspace)).toEqual(["./gradlew", "test"]);
201
201
  });
202
202
 
203
- test("throws error for unknown test framework", () => {
203
+ test("returns fallback for unknown test framework", () => {
204
204
  const workspace: WorkspaceInfo = {
205
205
  path: "/path/to/workspace",
206
206
  language: "unknown",
207
- packageManager: "unknown",
207
+ packageManager: "npm",
208
208
  framework: "unknown",
209
209
  testFramework: "unknown",
210
210
  };
211
211
 
212
- expect(() => buildTestCommand(workspace)).toThrow("Unsupported test framework");
212
+ // After fallback implementation, unknown should return npm test
213
+ expect(buildTestCommand(workspace)).toEqual(["npm", "test"]);
213
214
  });
214
215
  });
215
216