@ronkovic/aad 0.3.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/bin/aad.js +2 -0
  4. package/package.json +78 -0
  5. package/src/__tests__/e2e/pipeline-e2e.test.ts +279 -0
  6. package/src/__tests__/e2e/resume-e2e.test.ts +200 -0
  7. package/src/__tests__/integration/cli-smoke.test.ts +175 -0
  8. package/src/__tests__/integration/pipeline.test.ts +346 -0
  9. package/src/bun-imports.d.ts +14 -0
  10. package/src/main.ts +52 -0
  11. package/src/modules/claude-provider/__tests__/claude-cli.adapter.test.ts +277 -0
  12. package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +127 -0
  13. package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +347 -0
  14. package/src/modules/claude-provider/__tests__/effort-strategy.test.ts +212 -0
  15. package/src/modules/claude-provider/__tests__/provider-registry.test.ts +251 -0
  16. package/src/modules/claude-provider/__tests__/retry.test.ts +201 -0
  17. package/src/modules/claude-provider/claude-cli.adapter.ts +156 -0
  18. package/src/modules/claude-provider/claude-provider.port.ts +35 -0
  19. package/src/modules/claude-provider/claude-sdk.adapter.ts +217 -0
  20. package/src/modules/claude-provider/effort-strategy.ts +94 -0
  21. package/src/modules/claude-provider/index.ts +32 -0
  22. package/src/modules/claude-provider/provider-registry.ts +92 -0
  23. package/src/modules/claude-provider/retry.ts +81 -0
  24. package/src/modules/cli/__tests__/app.test.ts +160 -0
  25. package/src/modules/cli/__tests__/cleanup.test.ts +111 -0
  26. package/src/modules/cli/__tests__/commands.test.ts +186 -0
  27. package/src/modules/cli/__tests__/output.test.ts +329 -0
  28. package/src/modules/cli/__tests__/resume.test.ts +324 -0
  29. package/src/modules/cli/__tests__/run.test.ts +168 -0
  30. package/src/modules/cli/__tests__/shutdown.test.ts +168 -0
  31. package/src/modules/cli/__tests__/status.test.ts +144 -0
  32. package/src/modules/cli/app.ts +241 -0
  33. package/src/modules/cli/commands/cleanup.ts +120 -0
  34. package/src/modules/cli/commands/resume.ts +156 -0
  35. package/src/modules/cli/commands/run.ts +322 -0
  36. package/src/modules/cli/commands/status.ts +101 -0
  37. package/src/modules/cli/index.ts +29 -0
  38. package/src/modules/cli/output.ts +256 -0
  39. package/src/modules/cli/shutdown.ts +122 -0
  40. package/src/modules/dashboard/__tests__/api-routes.test.ts +204 -0
  41. package/src/modules/dashboard/__tests__/file-watcher.test.ts +34 -0
  42. package/src/modules/dashboard/__tests__/server.test.ts +120 -0
  43. package/src/modules/dashboard/__tests__/sse-broadcaster.test.ts +163 -0
  44. package/src/modules/dashboard/__tests__/sse-routes.test.ts +58 -0
  45. package/src/modules/dashboard/__tests__/state-aggregator.test.ts +330 -0
  46. package/src/modules/dashboard/index.ts +8 -0
  47. package/src/modules/dashboard/routes/api.ts +84 -0
  48. package/src/modules/dashboard/routes/sse.ts +37 -0
  49. package/src/modules/dashboard/server.ts +111 -0
  50. package/src/modules/dashboard/services/file-watcher.ts +36 -0
  51. package/src/modules/dashboard/services/sse-broadcaster.ts +81 -0
  52. package/src/modules/dashboard/services/state-aggregator.ts +132 -0
  53. package/src/modules/dashboard/ui/dashboard.html +405 -0
  54. package/src/modules/git-workspace/__tests__/branch-manager.test.ts +335 -0
  55. package/src/modules/git-workspace/__tests__/git-exec.test.ts +91 -0
  56. package/src/modules/git-workspace/__tests__/memory-sync.test.ts +273 -0
  57. package/src/modules/git-workspace/__tests__/merge-service.test.ts +286 -0
  58. package/src/modules/git-workspace/__tests__/settings-merge.test.ts +163 -0
  59. package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +247 -0
  60. package/src/modules/git-workspace/branch-manager.ts +191 -0
  61. package/src/modules/git-workspace/git-exec.ts +124 -0
  62. package/src/modules/git-workspace/index.ts +17 -0
  63. package/src/modules/git-workspace/memory-sync.ts +89 -0
  64. package/src/modules/git-workspace/merge-service.ts +156 -0
  65. package/src/modules/git-workspace/settings-merge.ts +95 -0
  66. package/src/modules/git-workspace/worktree-manager.ts +199 -0
  67. package/src/modules/logging/__tests__/log-store.test.ts +242 -0
  68. package/src/modules/logging/__tests__/logger.test.ts +81 -0
  69. package/src/modules/logging/__tests__/sse-transport.test.ts +93 -0
  70. package/src/modules/logging/index.ts +7 -0
  71. package/src/modules/logging/log-store.ts +80 -0
  72. package/src/modules/logging/logger.ts +55 -0
  73. package/src/modules/logging/transports/sse-transport.ts +28 -0
  74. package/src/modules/multi-repo/__tests__/multi-repo-planner.test.ts +93 -0
  75. package/src/modules/multi-repo/__tests__/repo-context.test.ts +79 -0
  76. package/src/modules/multi-repo/index.ts +12 -0
  77. package/src/modules/multi-repo/multi-repo-planner.ts +112 -0
  78. package/src/modules/multi-repo/repo-context.ts +71 -0
  79. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +10 -0
  80. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +10 -0
  81. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +13 -0
  82. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +10 -0
  83. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +10 -0
  84. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +5 -0
  85. package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +5 -0
  86. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +10 -0
  87. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +10 -0
  88. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +13 -0
  89. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +10 -0
  90. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +10 -0
  91. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +5 -0
  92. package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +5 -0
  93. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +10 -0
  94. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +10 -0
  95. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +13 -0
  96. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +10 -0
  97. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +10 -0
  98. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +5 -0
  99. package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +5 -0
  100. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +10 -0
  101. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +10 -0
  102. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +13 -0
  103. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +10 -0
  104. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +10 -0
  105. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +5 -0
  106. package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +5 -0
  107. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +10 -0
  108. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +10 -0
  109. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +13 -0
  110. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +10 -0
  111. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +10 -0
  112. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +5 -0
  113. package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +5 -0
  114. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +10 -0
  115. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +10 -0
  116. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +13 -0
  117. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +10 -0
  118. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +10 -0
  119. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +5 -0
  120. package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +5 -0
  121. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +10 -0
  122. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +10 -0
  123. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +13 -0
  124. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +10 -0
  125. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +10 -0
  126. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +5 -0
  127. package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +5 -0
  128. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +10 -0
  129. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +10 -0
  130. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +13 -0
  131. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +10 -0
  132. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +10 -0
  133. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +5 -0
  134. package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +5 -0
  135. package/src/modules/persistence/__tests__/file-lock.test.ts +141 -0
  136. package/src/modules/persistence/__tests__/index.test.ts +38 -0
  137. package/src/modules/persistence/__tests__/stores.test.ts +594 -0
  138. package/src/modules/persistence/file-lock.ts +158 -0
  139. package/src/modules/persistence/fs-run-store.ts +73 -0
  140. package/src/modules/persistence/fs-task-store.ts +152 -0
  141. package/src/modules/persistence/fs-worker-store.ts +116 -0
  142. package/src/modules/persistence/in-memory-stores.ts +98 -0
  143. package/src/modules/persistence/index.ts +60 -0
  144. package/src/modules/persistence/stores.port.ts +60 -0
  145. package/src/modules/planning/__tests__/file-conflict-validator.test.ts +256 -0
  146. package/src/modules/planning/__tests__/planning-service.test.ts +366 -0
  147. package/src/modules/planning/__tests__/project-detection.test.ts +707 -0
  148. package/src/modules/planning/file-conflict-validator.ts +135 -0
  149. package/src/modules/planning/index.ts +40 -0
  150. package/src/modules/planning/planning.service.ts +262 -0
  151. package/src/modules/planning/project-detection.ts +525 -0
  152. package/src/modules/plugin/__tests__/plugin-loader.test.ts +83 -0
  153. package/src/modules/plugin/__tests__/plugin-manager.test.ts +187 -0
  154. package/src/modules/plugin/index.ts +3 -0
  155. package/src/modules/plugin/plugin-loader.ts +46 -0
  156. package/src/modules/plugin/plugin-manager.ts +90 -0
  157. package/src/modules/plugin/plugin.types.ts +37 -0
  158. package/src/modules/process-manager/__tests__/process-manager.test.ts +210 -0
  159. package/src/modules/process-manager/__tests__/worker.test.ts +89 -0
  160. package/src/modules/process-manager/index.ts +5 -0
  161. package/src/modules/process-manager/process-manager.ts +193 -0
  162. package/src/modules/process-manager/worker.ts +106 -0
  163. package/src/modules/task-execution/__tests__/default-spawner.test.ts +154 -0
  164. package/src/modules/task-execution/__tests__/executor.test.ts +760 -0
  165. package/src/modules/task-execution/__tests__/implementer-green.test.ts +286 -0
  166. package/src/modules/task-execution/__tests__/merge-phase.test.ts +368 -0
  167. package/src/modules/task-execution/__tests__/reviewer.test.ts +302 -0
  168. package/src/modules/task-execution/__tests__/tester-red.test.ts +281 -0
  169. package/src/modules/task-execution/__tests__/tester-verify.test.ts +313 -0
  170. package/src/modules/task-execution/executor.ts +303 -0
  171. package/src/modules/task-execution/index.ts +45 -0
  172. package/src/modules/task-execution/phases/default-spawner.ts +49 -0
  173. package/src/modules/task-execution/phases/implementer-green.ts +100 -0
  174. package/src/modules/task-execution/phases/merge.ts +122 -0
  175. package/src/modules/task-execution/phases/reviewer.ts +160 -0
  176. package/src/modules/task-execution/phases/tester-red.ts +100 -0
  177. package/src/modules/task-execution/phases/tester-verify.ts +120 -0
  178. package/src/modules/task-queue/__tests__/dependency-resolver.test.ts +456 -0
  179. package/src/modules/task-queue/__tests__/dispatcher.test.ts +824 -0
  180. package/src/modules/task-queue/__tests__/task-plan.test.ts +122 -0
  181. package/src/modules/task-queue/__tests__/task.test.ts +130 -0
  182. package/src/modules/task-queue/dependency-resolver.ts +171 -0
  183. package/src/modules/task-queue/dispatcher.ts +372 -0
  184. package/src/modules/task-queue/index.ts +16 -0
  185. package/src/modules/task-queue/task-plan.ts +40 -0
  186. package/src/modules/task-queue/task.ts +67 -0
  187. package/src/shared/__tests__/config.test.ts +204 -0
  188. package/src/shared/__tests__/errors.test.ts +285 -0
  189. package/src/shared/__tests__/events.test.ts +496 -0
  190. package/src/shared/__tests__/types.test.ts +360 -0
  191. package/src/shared/config.ts +133 -0
  192. package/src/shared/errors.ts +128 -0
  193. package/src/shared/events.ts +171 -0
  194. package/src/shared/types.ts +143 -0
  195. package/tsconfig.json +30 -0
@@ -0,0 +1,135 @@
1
+ // File Conflict Validator - port from .aad/scripts/verify-file-conflicts.sh
2
+ // Validates that tasks modifying the same file have proper dependency relationships
3
+
4
+ import type { Task, TaskId } from "../../shared/types";
5
+
6
+ export interface FileConflict {
7
+ filePath: string;
8
+ ownerTask: TaskId;
9
+ conflictingTask: TaskId;
10
+ hasDependency: boolean;
11
+ }
12
+
13
+ export interface FileConflictResult {
14
+ valid: boolean;
15
+ conflicts: FileConflict[];
16
+ }
17
+
18
+ /**
19
+ * Check if targetTask is a transitive dependency of taskId
20
+ * (i.e., taskId depends on targetTask directly or indirectly)
21
+ */
22
+ function checkTransitiveDependency(
23
+ taskId: TaskId,
24
+ targetTask: TaskId,
25
+ tasks: Task[],
26
+ visited: Set<TaskId> = new Set()
27
+ ): boolean {
28
+ // Prevent circular reference
29
+ if (visited.has(taskId)) {
30
+ return false;
31
+ }
32
+
33
+ const task = tasks.find((t) => t.taskId === taskId);
34
+ if (!task) {
35
+ return false;
36
+ }
37
+
38
+ visited.add(taskId);
39
+
40
+ // Check direct dependencies
41
+ for (const dep of task.dependsOn) {
42
+ if (dep === targetTask) {
43
+ return true;
44
+ }
45
+
46
+ // Recursively check transitive dependencies
47
+ if (checkTransitiveDependency(dep, targetTask, tasks, visited)) {
48
+ return true;
49
+ }
50
+ }
51
+
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Validates file conflicts in task plan
57
+ * Port from .aad/scripts/verify-file-conflicts.sh
58
+ *
59
+ * Rules:
60
+ * - If multiple tasks modify the same file, the later task MUST depend on the earlier task
61
+ * - Dependencies can be direct or transitive
62
+ * - Tasks are processed in priority order (lower priority number = higher priority)
63
+ */
64
+ export function validateFileConflicts(tasks: Task[]): FileConflictResult {
65
+ const conflicts: FileConflict[] = [];
66
+ const fileOwners = new Map<string, TaskId>();
67
+
68
+ // Sort by priority (ascending: lower number = higher priority)
69
+ const sortedTasks = [...tasks].sort((a, b) => a.priority - b.priority);
70
+
71
+ for (const task of sortedTasks) {
72
+ // Skip tasks with no files to modify
73
+ if (!task.filesToModify || task.filesToModify.length === 0) {
74
+ continue;
75
+ }
76
+
77
+ for (const filePath of task.filesToModify) {
78
+ // Skip empty file paths
79
+ if (!filePath || filePath.trim() === "") {
80
+ continue;
81
+ }
82
+
83
+ const ownerTask = fileOwners.get(filePath);
84
+
85
+ if (ownerTask) {
86
+ // File conflict detected
87
+ // Check if current task depends on the owner task (direct or transitive)
88
+ const hasDependency = checkTransitiveDependency(
89
+ task.taskId,
90
+ ownerTask,
91
+ tasks
92
+ );
93
+
94
+ if (!hasDependency) {
95
+ // Invalid: task modifies a file but doesn't depend on the owner
96
+ conflicts.push({
97
+ filePath,
98
+ ownerTask,
99
+ conflictingTask: task.taskId,
100
+ hasDependency: false,
101
+ });
102
+ }
103
+ // If hasDependency is true, the conflict is valid (tasks are properly ordered)
104
+ } else {
105
+ // This task is the first to claim this file
106
+ fileOwners.set(filePath, task.taskId);
107
+ }
108
+ }
109
+ }
110
+
111
+ return {
112
+ valid: conflicts.length === 0,
113
+ conflicts,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Format file conflict result as human-readable error messages
119
+ */
120
+ export function formatConflictErrors(result: FileConflictResult): string[] {
121
+ if (result.valid) {
122
+ return [];
123
+ }
124
+
125
+ return result.conflicts.map((conflict) => {
126
+ return [
127
+ `⚠️ File conflict detected:`,
128
+ ` File: ${conflict.filePath}`,
129
+ ` Owner: ${conflict.ownerTask}`,
130
+ ` Conflicting task: ${conflict.conflictingTask}`,
131
+ ` ❌ ERROR: Task ${conflict.conflictingTask} should depend on ${conflict.ownerTask} but doesn't`,
132
+ ` Please update task_plan.json to add this dependency`,
133
+ ].join("\n");
134
+ });
135
+ }
@@ -0,0 +1,40 @@
1
+ // Planning Module - Barrel Export
2
+ // Provides task planning capabilities: project detection, file conflict validation, and task plan generation
3
+
4
+ export {
5
+ detectProjectType,
6
+ detectPackageManager,
7
+ detectFramework,
8
+ detectTestFramework,
9
+ detectOrm,
10
+ detectArchitecturePattern,
11
+ isMonorepo,
12
+ detectWorkspaces,
13
+ analyzeProject,
14
+ createBunFileChecker,
15
+ } from "./project-detection";
16
+
17
+ export type {
18
+ ProjectType,
19
+ PackageManager,
20
+ TestFramework,
21
+ Framework,
22
+ OrmType,
23
+ ArchitecturePattern,
24
+ WorkspaceInfo,
25
+ ProjectAnalysis,
26
+ FileChecker,
27
+ } from "./project-detection";
28
+
29
+ export {
30
+ validateFileConflicts,
31
+ formatConflictErrors,
32
+ } from "./file-conflict-validator";
33
+
34
+ export type {
35
+ FileConflict,
36
+ FileConflictResult,
37
+ } from "./file-conflict-validator";
38
+
39
+ export { PlanningService } from "./planning.service";
40
+ export type { PlanTasksParams } from "./planning.service";
@@ -0,0 +1,262 @@
1
+ // Planning Service - orchestrates task planning workflow
2
+ // Port from .aad/scripts/run-parallel.sh run_splitter()
3
+
4
+ import type { ClaudeProvider, SubagentConfig } from "../claude-provider";
5
+ import type { EventBus } from "../../shared/events";
6
+ import type { Config } from "../../shared/config";
7
+ import type { RunId, TaskPlan } from "../../shared/types";
8
+ import type pino from "pino";
9
+ import { PlanningError } from "../../shared/errors";
10
+ import { parseTaskPlan, validateTaskPlan } from "../task-queue";
11
+ import { validateFileConflicts, formatConflictErrors } from "./file-conflict-validator";
12
+ import { analyzeProject, createBunFileChecker, type FileChecker } from "./project-detection";
13
+
14
+ export interface PlanTasksParams {
15
+ runId: RunId;
16
+ parentBranch: string;
17
+ requirementsPath: string;
18
+ targetDocsDir: string;
19
+ projectRoot?: string;
20
+ }
21
+
22
+ export class PlanningService {
23
+ private fileChecker: FileChecker;
24
+
25
+ constructor(
26
+ private claudeProvider: ClaudeProvider,
27
+ private eventBus: EventBus,
28
+ private config: Config,
29
+ private logger: pino.Logger,
30
+ options?: { fileChecker?: FileChecker }
31
+ ) {
32
+ this.fileChecker = options?.fileChecker ?? createBunFileChecker();
33
+ }
34
+
35
+ /**
36
+ * Plan tasks from requirements
37
+ * Orchestrates: project analysis → Claude splitter → file conflict validation
38
+ */
39
+ async planTasks(params: PlanTasksParams): Promise<TaskPlan> {
40
+ const { runId, parentBranch, requirementsPath, targetDocsDir, projectRoot = process.cwd() } = params;
41
+
42
+ this.logger.info({ runId, requirementsPath }, "Planning tasks started");
43
+ this.eventBus.emit({
44
+ type: "planning:started",
45
+ requirementsPath,
46
+ });
47
+
48
+ try {
49
+ // 1. Project analysis (optional context for splitter)
50
+ const projectAnalysis = await analyzeProject(projectRoot, this.fileChecker);
51
+ this.logger.debug({ projectAnalysis }, "Project analysis completed");
52
+
53
+ // 2. Build splitter prompt
54
+ const prompt = this.buildSplitterPrompt({
55
+ runId,
56
+ parentBranch,
57
+ requirementsPath,
58
+ targetDocsDir,
59
+ projectAnalysis,
60
+ });
61
+
62
+ // 3. Call Claude splitter
63
+ this.logger.info("Calling Claude splitter agent");
64
+ const subagents = this.config.teams.splitter ? this.buildSplitterSubagents() : undefined;
65
+ const response = await this.claudeProvider.call({
66
+ prompt,
67
+ ...(subagents ? { subagents } : {}),
68
+ });
69
+
70
+ if (response.exitCode !== 0) {
71
+ throw new PlanningError(`Claude splitter failed with exit code ${response.exitCode}`, {
72
+ runId,
73
+ exitCode: response.exitCode,
74
+ output: response.result,
75
+ });
76
+ }
77
+
78
+ // 4. Parse task plan from Claude output
79
+ const taskPlanJsonStr = this.extractTaskPlanFromOutput(response.result);
80
+ const taskPlanJson = JSON.parse(taskPlanJsonStr);
81
+ const taskPlan = parseTaskPlan(taskPlanJson);
82
+
83
+ this.logger.debug({ taskCount: taskPlan.tasks.length }, "Task plan parsed");
84
+
85
+ // 5. Validate task plan structure (circular dependencies)
86
+ try {
87
+ validateTaskPlan(taskPlan.tasks);
88
+ } catch (error) {
89
+ const errorMessage = error instanceof Error ? error.message : String(error);
90
+ throw new PlanningError(`Invalid task plan: ${errorMessage}`, {
91
+ runId,
92
+ error: errorMessage,
93
+ });
94
+ }
95
+
96
+ // 6. Validate file conflicts
97
+ const conflictResult = validateFileConflicts(taskPlan.tasks);
98
+ if (!conflictResult.valid) {
99
+ const errorMessages = formatConflictErrors(conflictResult);
100
+ throw new PlanningError(`File conflicts detected:\n${errorMessages.join("\n\n")}`, {
101
+ runId,
102
+ conflicts: conflictResult.conflicts,
103
+ });
104
+ }
105
+
106
+ this.logger.info({ taskCount: taskPlan.tasks.length }, "Planning completed successfully");
107
+ this.eventBus.emit({
108
+ type: "planning:completed",
109
+ taskCount: taskPlan.tasks.length,
110
+ });
111
+
112
+ return taskPlan;
113
+ } catch (error) {
114
+ const errorMessage = error instanceof Error ? error.message : String(error);
115
+ this.logger.error({ error, runId }, "Planning failed");
116
+ this.eventBus.emit({
117
+ type: "planning:failed",
118
+ error: errorMessage,
119
+ });
120
+ throw error;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Build splitter prompt (teams mode or solo mode)
126
+ */
127
+ private buildSplitterPrompt(params: {
128
+ runId: RunId;
129
+ parentBranch: string;
130
+ requirementsPath: string;
131
+ targetDocsDir: string;
132
+ projectAnalysis: unknown;
133
+ }): string {
134
+ const { runId, parentBranch, requirementsPath } = params;
135
+
136
+ const taskPlanFormat = JSON.stringify(
137
+ {
138
+ run_id: runId,
139
+ parent_branch: parentBranch,
140
+ tasks: [
141
+ {
142
+ task_id: "task-001",
143
+ title: "タスクのタイトル",
144
+ description: "詳細な説明",
145
+ files_to_modify: ["file1.js", "file2.js"],
146
+ depends_on: ["task-000"],
147
+ priority: 1,
148
+ },
149
+ ],
150
+ },
151
+ null,
152
+ 2
153
+ );
154
+
155
+ if (this.config.teams.splitter) {
156
+ // Teams mode: subagents handle analysis, main agent integrates
157
+ return `splitterエージェントとして、サブエージェントの分析結果を統合し、要件をタスクに分割してください。
158
+
159
+ サブエージェント(codebase-analyzer, requirement-analyzer, dependency-mapper)が自動的に並列分析を行います。
160
+ それぞれの結果を統合して、最終的なタスクプランを作成してください。
161
+
162
+ 要件: ${requirementsPath}
163
+ Run ID: ${runId}
164
+ 親ブランチ: ${parentBranch}
165
+
166
+ 以下の手順を実行してください:
167
+ 1. サブエージェントの分析結果を統合する
168
+ 2. タスクに分割し、以下のJSON形式で標準出力に出力してください(ファイル生成ではなく、コンソールに直接出力):
169
+ ${taskPlanFormat}
170
+ 3. 各タスクの files_to_modify を明確にする
171
+ 4. 依存関係 (depends_on) を設定する
172
+ ⚠️ 重要: 同じファイルを変更する複数のタスクがある場合、必ず後のタスクを前のタスクに依存させてください
173
+ これにより、ファイル衝突を防ぎます
174
+ 5. priority を設定する
175
+
176
+ ⚠️ 重要: 最終的な出力は、上記のJSON形式のみを標準出力に出力してください。説明文やコードブロック記法(\`\`\`)は不要です。`;
177
+ } else {
178
+ // Solo mode: traditional prompt
179
+ return `splitterエージェントとして、以下の要件を分割してください:
180
+
181
+ 要件: ${requirementsPath}
182
+ Run ID: ${runId}
183
+ 親ブランチ: ${parentBranch}
184
+
185
+ 以下の手順を実行してください:
186
+ 1. 要件ファイル/ディレクトリを読み込む(ディレクトリの場合は配下のファイルを全て読む)
187
+ 2. タスクに分割し、以下のJSON形式で標準出力に出力してください(ファイル生成ではなく、コンソールに直接出力):
188
+ ${taskPlanFormat}
189
+ 3. 各タスクの files_to_modify を明確にする
190
+ 4. 依存関係 (depends_on) を設定する
191
+ ⚠️ 重要: 同じファイルを変更する複数のタスクがある場合、必ず後のタスクを前のタスクに依存させてください
192
+ これにより、ファイル衝突を防ぎます
193
+ 5. priority を設定する
194
+
195
+ ⚠️ 重要: 最終的な出力は、上記のJSON形式のみを標準出力に出力してください。説明文やコードブロック記法(\`\`\`)は不要です。`;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Build subagent configs for splitter teams mode
201
+ */
202
+ private buildSplitterSubagents(): SubagentConfig[] {
203
+ return [
204
+ {
205
+ name: "codebase-analyzer",
206
+ prompt: `コードベース分析の専門家として、プロジェクトのディレクトリ構造、既存コード、技術スタックを分析してください。
207
+ 以下の観点で報告してください:
208
+ - プロジェクト構造とモジュール構成
209
+ - 使用されている技術スタック(言語、フレームワーク、ライブラリ)
210
+ - コーディング規約やパターン
211
+ - 既存のテスト構成`,
212
+ },
213
+ {
214
+ name: "requirement-analyzer",
215
+ prompt: `要件分析の専門家として、要件ドキュメントを分析してください。
216
+ 以下の観点で報告してください:
217
+ - ユーザーストーリーの抽出
218
+ - 受入基準の特定
219
+ - 機能要件と非機能要件の分類
220
+ - 実装の優先度提案`,
221
+ },
222
+ {
223
+ name: "dependency-mapper",
224
+ prompt: `依存関係分析の専門家として、モジュール間の依存関係を分析してください。
225
+ 以下の観点で報告してください:
226
+ - モジュール間の依存グラフ
227
+ - 並列実行可能なタスクの特定
228
+ - ファイル変更の競合リスク
229
+ - 推奨される実装順序`,
230
+ },
231
+ ];
232
+ }
233
+
234
+ /**
235
+ * Extract task_plan.json from Claude output
236
+ * Claude may return the JSON directly or wrapped in markdown code block
237
+ */
238
+ private extractTaskPlanFromOutput(output: string): string {
239
+ // Try to parse directly
240
+ try {
241
+ JSON.parse(output);
242
+ return output;
243
+ } catch {
244
+ // Not valid JSON, try to extract from markdown code block
245
+ }
246
+
247
+ // Try to extract from ```json ... ``` block
248
+ const jsonBlockMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
249
+ if (jsonBlockMatch?.[1]) {
250
+ return jsonBlockMatch[1];
251
+ }
252
+
253
+ // Try to extract from ``` ... ``` block
254
+ const codeBlockMatch = output.match(/```\s*([\s\S]*?)\s*```/);
255
+ if (codeBlockMatch?.[1]) {
256
+ return codeBlockMatch[1];
257
+ }
258
+
259
+ // Return as-is and let parseTaskPlan handle validation
260
+ return output;
261
+ }
262
+ }