@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,707 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import type { FileChecker } from "../project-detection";
3
+ import {
4
+ detectProjectType,
5
+ detectPackageManager,
6
+ detectFramework,
7
+ detectTestFramework,
8
+ detectOrm,
9
+ detectArchitecturePattern,
10
+ isMonorepo,
11
+ detectWorkspaces,
12
+ analyzeProject,
13
+ } from "../project-detection";
14
+
15
+ // Mock FileChecker for testing
16
+ function createMockFileChecker(files: Record<string, string>): FileChecker {
17
+ return {
18
+ async exists(filePath: string): Promise<boolean> {
19
+ // Extract basename for matching
20
+ const basename = filePath.split("/").pop() || filePath;
21
+ return basename in files || filePath in files;
22
+ },
23
+ async readText(filePath: string): Promise<string> {
24
+ const basename = filePath.split("/").pop() || filePath;
25
+ if (basename in files) {
26
+ if (typeof files[basename] === "string") return files[basename];
27
+ }
28
+ if (filePath in files) {
29
+ if (typeof files[filePath] === "string") return files[filePath];
30
+ }
31
+ throw new Error(`File not found: ${filePath}`);
32
+ },
33
+ async glob(pattern: string, _: string): Promise<string[]> {
34
+ // Simple mock: convert glob pattern to regex
35
+ const regexPattern = pattern
36
+ .replace(/\./g, "\\.")
37
+ .replace(/\*\*/g, "___DOUBLESTAR___")
38
+ .replace(/\*/g, "[^/]*")
39
+ .replace(/___DOUBLESTAR___/g, ".*");
40
+ const regex = new RegExp(`^${regexPattern}$`);
41
+ return Object.keys(files).filter((f) => regex.test(f));
42
+ },
43
+ };
44
+ }
45
+
46
+ describe("detectProjectType", () => {
47
+ test("detects Go project", async () => {
48
+ const checker = createMockFileChecker({ "go.mod": "" });
49
+ const result = await detectProjectType("/test", checker);
50
+ expect(result).toBe("go");
51
+ });
52
+
53
+ test("detects Go workspace", async () => {
54
+ const checker = createMockFileChecker({ "go.work": "" });
55
+ const result = await detectProjectType("/test", checker);
56
+ expect(result).toBe("go-workspace");
57
+ });
58
+
59
+ test("detects Rust project", async () => {
60
+ const checker = createMockFileChecker({ "Cargo.toml": "" });
61
+ const result = await detectProjectType("/test", checker);
62
+ expect(result).toBe("rust");
63
+ });
64
+
65
+ test("detects Python project with pyproject.toml", async () => {
66
+ const checker = createMockFileChecker({ "pyproject.toml": "" });
67
+ const result = await detectProjectType("/test", checker);
68
+ expect(result).toBe("python");
69
+ });
70
+
71
+ test("detects Python project with requirements.txt", async () => {
72
+ const checker = createMockFileChecker({ "requirements.txt": "" });
73
+ const result = await detectProjectType("/test", checker);
74
+ expect(result).toBe("python");
75
+ });
76
+
77
+ test("detects Next.js project", async () => {
78
+ const checker = createMockFileChecker({
79
+ "package.json": '{"dependencies": {"next": "^14.0.0"}}',
80
+ });
81
+ const result = await detectProjectType("/test", checker);
82
+ expect(result).toBe("nextjs");
83
+ });
84
+
85
+ test("detects Express project", async () => {
86
+ const checker = createMockFileChecker({
87
+ "package.json": '{"dependencies": {"express": "^4.0.0"}}',
88
+ });
89
+ const result = await detectProjectType("/test", checker);
90
+ expect(result).toBe("express");
91
+ });
92
+
93
+ test("detects React project", async () => {
94
+ const checker = createMockFileChecker({
95
+ "package.json": '{"dependencies": {"react": "^18.0.0"}}',
96
+ });
97
+ const result = await detectProjectType("/test", checker);
98
+ expect(result).toBe("react");
99
+ });
100
+
101
+ test("detects generic Node.js project", async () => {
102
+ const checker = createMockFileChecker({
103
+ "package.json": '{"dependencies": {"lodash": "^4.0.0"}}',
104
+ });
105
+ const result = await detectProjectType("/test", checker);
106
+ expect(result).toBe("nodejs");
107
+ });
108
+
109
+ test("detects Terraform project", async () => {
110
+ const checker = createMockFileChecker({ "main.tf": "" });
111
+ const result = await detectProjectType("/test", checker);
112
+ expect(result).toBe("terraform");
113
+ });
114
+
115
+ test("returns unknown for unrecognized project", async () => {
116
+ const checker = createMockFileChecker({ "README.md": "" });
117
+ const result = await detectProjectType("/test", checker);
118
+ expect(result).toBe("unknown");
119
+ });
120
+ });
121
+
122
+ describe("detectPackageManager", () => {
123
+ test("detects uv", async () => {
124
+ const checker = createMockFileChecker({ "uv.lock": "" });
125
+ const result = await detectPackageManager("/test", checker);
126
+ expect(result).toBe("uv");
127
+ });
128
+
129
+ test("detects poetry", async () => {
130
+ const checker = createMockFileChecker({ "poetry.lock": "" });
131
+ const result = await detectPackageManager("/test", checker);
132
+ expect(result).toBe("poetry");
133
+ });
134
+
135
+ test("detects pipenv", async () => {
136
+ const checker = createMockFileChecker({ "Pipfile.lock": "" });
137
+ const result = await detectPackageManager("/test", checker);
138
+ expect(result).toBe("pipenv");
139
+ });
140
+
141
+ test("detects pip", async () => {
142
+ const checker = createMockFileChecker({ "requirements.txt": "" });
143
+ const result = await detectPackageManager("/test", checker);
144
+ expect(result).toBe("pip");
145
+ });
146
+
147
+ test("detects pnpm", async () => {
148
+ const checker = createMockFileChecker({ "pnpm-lock.yaml": "" });
149
+ const result = await detectPackageManager("/test", checker);
150
+ expect(result).toBe("pnpm");
151
+ });
152
+
153
+ test("detects yarn", async () => {
154
+ const checker = createMockFileChecker({ "yarn.lock": "" });
155
+ const result = await detectPackageManager("/test", checker);
156
+ expect(result).toBe("yarn");
157
+ });
158
+
159
+ test("detects npm", async () => {
160
+ const checker = createMockFileChecker({ "package-lock.json": "" });
161
+ const result = await detectPackageManager("/test", checker);
162
+ expect(result).toBe("npm");
163
+ });
164
+
165
+ test("detects bun", async () => {
166
+ const checker = createMockFileChecker({ "bun.lockb": "" });
167
+ const result = await detectPackageManager("/test", checker);
168
+ expect(result).toBe("bun");
169
+ });
170
+
171
+ test("detects cargo", async () => {
172
+ const checker = createMockFileChecker({ "Cargo.lock": "" });
173
+ const result = await detectPackageManager("/test", checker);
174
+ expect(result).toBe("cargo");
175
+ });
176
+
177
+ test("detects go", async () => {
178
+ const checker = createMockFileChecker({ "go.sum": "" });
179
+ const result = await detectPackageManager("/test", checker);
180
+ expect(result).toBe("go");
181
+ });
182
+
183
+ test("returns unknown for unrecognized package manager", async () => {
184
+ const checker = createMockFileChecker({ "README.md": "" });
185
+ const result = await detectPackageManager("/test", checker);
186
+ expect(result).toBe("unknown");
187
+ });
188
+ });
189
+
190
+ describe("detectFramework", () => {
191
+ test("detects FastAPI", async () => {
192
+ const checker = createMockFileChecker({
193
+ "pyproject.toml": '[tool.poetry.dependencies]\nfastapi = "^0.100.0"',
194
+ });
195
+ const result = await detectFramework("/test", "python", checker);
196
+ expect(result).toBe("fastapi");
197
+ });
198
+
199
+ test("detects Django", async () => {
200
+ const checker = createMockFileChecker({
201
+ "pyproject.toml": '[tool.poetry.dependencies]\ndjango = "^4.0"',
202
+ });
203
+ const result = await detectFramework("/test", "python", checker);
204
+ expect(result).toBe("django");
205
+ });
206
+
207
+ test("detects Actix for Rust", async () => {
208
+ const checker = createMockFileChecker({
209
+ "Cargo.toml": '[dependencies]\nactix-web = "4.0"',
210
+ });
211
+ const result = await detectFramework("/test", "rust", checker);
212
+ expect(result).toBe("actix");
213
+ });
214
+
215
+ test("returns unknown for no framework", async () => {
216
+ const checker = createMockFileChecker({ "pyproject.toml": "" });
217
+ const result = await detectFramework("/test", "python", checker);
218
+ expect(result).toBe("unknown");
219
+ });
220
+
221
+ test("detects Flask for Python", async () => {
222
+ const checker = createMockFileChecker({
223
+ "pyproject.toml": '[tool.poetry.dependencies]\nflask = "^3.0"',
224
+ });
225
+ const result = await detectFramework("/test", "python", checker);
226
+ expect(result).toBe("flask");
227
+ });
228
+
229
+ test("returns unknown for Go", async () => {
230
+ const checker = createMockFileChecker({});
231
+ const result = await detectFramework("/test", "go", checker);
232
+ expect(result).toBe("unknown");
233
+ });
234
+
235
+ test("returns unknown for Go workspace", async () => {
236
+ const checker = createMockFileChecker({});
237
+ const result = await detectFramework("/test", "go-workspace", checker);
238
+ expect(result).toBe("unknown");
239
+ });
240
+
241
+ test("returns nextjs for nextjs project type", async () => {
242
+ const checker = createMockFileChecker({});
243
+ const result = await detectFramework("/test", "nextjs", checker);
244
+ expect(result).toBe("nextjs");
245
+ });
246
+
247
+ test("returns express for express project type", async () => {
248
+ const checker = createMockFileChecker({});
249
+ const result = await detectFramework("/test", "express", checker);
250
+ expect(result).toBe("express");
251
+ });
252
+
253
+ test("returns react for react project type", async () => {
254
+ const checker = createMockFileChecker({});
255
+ const result = await detectFramework("/test", "react", checker);
256
+ expect(result).toBe("react");
257
+ });
258
+
259
+ test("returns unknown for Rust without actix", async () => {
260
+ const checker = createMockFileChecker({ "Cargo.toml": "[dependencies]" });
261
+ const result = await detectFramework("/test", "rust", checker);
262
+ expect(result).toBe("unknown");
263
+ });
264
+
265
+ test("returns unknown for unknown project type", async () => {
266
+ const checker = createMockFileChecker({});
267
+ const result = await detectFramework("/test", "unknown", checker);
268
+ expect(result).toBe("unknown");
269
+ });
270
+
271
+ test("returns unknown for python without pyproject.toml", async () => {
272
+ const checker = createMockFileChecker({});
273
+ const result = await detectFramework("/test", "python", checker);
274
+ expect(result).toBe("unknown");
275
+ });
276
+
277
+ test("returns unknown for rust without Cargo.toml", async () => {
278
+ const checker = createMockFileChecker({});
279
+ const result = await detectFramework("/test", "rust", checker);
280
+ expect(result).toBe("unknown");
281
+ });
282
+ });
283
+
284
+ describe("detectTestFramework", () => {
285
+ test("detects go-test for Go", async () => {
286
+ const checker = createMockFileChecker({});
287
+ const result = await detectTestFramework("/test", "go", checker);
288
+ expect(result).toBe("go-test");
289
+ });
290
+
291
+ test("detects cargo-test for Rust", async () => {
292
+ const checker = createMockFileChecker({});
293
+ const result = await detectTestFramework("/test", "rust", checker);
294
+ expect(result).toBe("cargo-test");
295
+ });
296
+
297
+ test("detects pytest for Python", async () => {
298
+ const checker = createMockFileChecker({
299
+ "pyproject.toml": '[tool.poetry.dependencies]\npytest = "^7.0"',
300
+ });
301
+ const result = await detectTestFramework("/test", "python", checker);
302
+ expect(result).toBe("pytest");
303
+ });
304
+
305
+ test("defaults to unittest for Python", async () => {
306
+ const checker = createMockFileChecker({ "pyproject.toml": "" });
307
+ const result = await detectTestFramework("/test", "python", checker);
308
+ expect(result).toBe("unittest");
309
+ });
310
+
311
+ test("detects vitest for Node.js", async () => {
312
+ const checker = createMockFileChecker({
313
+ "package.json": '{"devDependencies": {"vitest": "^1.0.0"}}',
314
+ });
315
+ const result = await detectTestFramework("/test", "nodejs", checker);
316
+ expect(result).toBe("vitest");
317
+ });
318
+
319
+ test("detects jest for Node.js", async () => {
320
+ const checker = createMockFileChecker({
321
+ "package.json": '{"devDependencies": {"jest": "^29.0.0"}}',
322
+ });
323
+ const result = await detectTestFramework("/test", "nodejs", checker);
324
+ expect(result).toBe("jest");
325
+ });
326
+
327
+ test("detects bun:test for Bun projects", async () => {
328
+ const checker = createMockFileChecker({
329
+ "package.json": '{"devDependencies": {"bun-types": "^1.0.0"}}',
330
+ });
331
+ const result = await detectTestFramework("/test", "nodejs", checker);
332
+ expect(result).toBe("bun:test");
333
+ });
334
+
335
+ test("returns unknown for unknown project type", async () => {
336
+ const checker = createMockFileChecker({});
337
+ const result = await detectTestFramework("/test", "unknown", checker);
338
+ expect(result).toBe("unknown");
339
+ });
340
+
341
+ test("detects go-test for go-workspace", async () => {
342
+ const checker = createMockFileChecker({});
343
+ const result = await detectTestFramework("/test", "go-workspace", checker);
344
+ expect(result).toBe("go-test");
345
+ });
346
+
347
+ test("detects terraform-validate", async () => {
348
+ const checker = createMockFileChecker({});
349
+ const result = await detectTestFramework("/test", "terraform", checker);
350
+ expect(result).toBe("terraform-validate");
351
+ });
352
+
353
+ test("detects mocha for nodejs", async () => {
354
+ const checker = createMockFileChecker({
355
+ "package.json": '{"devDependencies": {"mocha": "^10.0.0"}}',
356
+ });
357
+ const result = await detectTestFramework("/test", "nodejs", checker);
358
+ expect(result).toBe("mocha");
359
+ });
360
+
361
+ test("returns unknown for nodejs without test framework", async () => {
362
+ const checker = createMockFileChecker({
363
+ "package.json": '{"dependencies": {"lodash": "^4.0"}}',
364
+ });
365
+ const result = await detectTestFramework("/test", "nodejs", checker);
366
+ expect(result).toBe("unknown");
367
+ });
368
+
369
+ test("returns unknown for nodejs without package.json", async () => {
370
+ const checker = createMockFileChecker({});
371
+ const result = await detectTestFramework("/test", "nodejs", checker);
372
+ expect(result).toBe("unknown");
373
+ });
374
+
375
+ test("detects python unittest without pyproject.toml", async () => {
376
+ const checker = createMockFileChecker({});
377
+ const result = await detectTestFramework("/test", "python", checker);
378
+ expect(result).toBe("unittest");
379
+ });
380
+
381
+ test("detects bun:test via @types/bun", async () => {
382
+ const checker = createMockFileChecker({
383
+ "package.json": '{"devDependencies": {"@types/bun": "^1.0.0"}}',
384
+ });
385
+ const result = await detectTestFramework("/test", "nextjs", checker);
386
+ expect(result).toBe("bun:test");
387
+ });
388
+
389
+ test("detects test framework for react project type", async () => {
390
+ const checker = createMockFileChecker({
391
+ "package.json": '{"devDependencies": {"vitest": "^1.0.0"}}',
392
+ });
393
+ const result = await detectTestFramework("/test", "react", checker);
394
+ expect(result).toBe("vitest");
395
+ });
396
+
397
+ test("detects test framework for express project type", async () => {
398
+ const checker = createMockFileChecker({
399
+ "package.json": '{"devDependencies": {"jest": "^29.0.0"}}',
400
+ });
401
+ const result = await detectTestFramework("/test", "express", checker);
402
+ expect(result).toBe("jest");
403
+ });
404
+ });
405
+
406
+ describe("detectOrm", () => {
407
+ test("detects GORM for Go", async () => {
408
+ const checker = createMockFileChecker({
409
+ "go.mod": "module example\n\nrequire gorm.io/gorm v1.25.0",
410
+ });
411
+ const result = await detectOrm("/test", "go", checker);
412
+ expect(result).toBe("gorm");
413
+ });
414
+
415
+ test("detects SQLAlchemy for Python", async () => {
416
+ const checker = createMockFileChecker({
417
+ "pyproject.toml": '[tool.poetry.dependencies]\nsqlalchemy = "^2.0"',
418
+ });
419
+ const result = await detectOrm("/test", "python", checker);
420
+ expect(result).toBe("sqlalchemy");
421
+ });
422
+
423
+ test("detects Diesel for Rust", async () => {
424
+ const checker = createMockFileChecker({
425
+ "Cargo.toml": '[dependencies]\ndiesel = "2.0"',
426
+ });
427
+ const result = await detectOrm("/test", "rust", checker);
428
+ expect(result).toBe("diesel");
429
+ });
430
+
431
+ test("detects Prisma for Node.js", async () => {
432
+ const checker = createMockFileChecker({
433
+ "package.json": '{"devDependencies": {"prisma": "^5.0.0"}}',
434
+ });
435
+ const result = await detectOrm("/test", "nodejs", checker);
436
+ expect(result).toBe("prisma");
437
+ });
438
+
439
+ test("returns unknown for no ORM", async () => {
440
+ const checker = createMockFileChecker({ "package.json": "{}" });
441
+ const result = await detectOrm("/test", "nodejs", checker);
442
+ expect(result).toBe("unknown");
443
+ });
444
+
445
+ test("detects sqlc for Go", async () => {
446
+ const checker = createMockFileChecker({ "sqlc.yaml": "" });
447
+ const result = await detectOrm("/test", "go", checker);
448
+ expect(result).toBe("sqlc");
449
+ });
450
+
451
+ test("detects sqlx for Go", async () => {
452
+ const checker = createMockFileChecker({
453
+ "go.mod": "module example\n\nrequire jmoiron/sqlx v1.0",
454
+ });
455
+ const result = await detectOrm("/test", "go", checker);
456
+ expect(result).toBe("sqlx");
457
+ });
458
+
459
+ test("returns unknown for Go without ORM", async () => {
460
+ const checker = createMockFileChecker({ "go.mod": "module example" });
461
+ const result = await detectOrm("/test", "go", checker);
462
+ expect(result).toBe("unknown");
463
+ });
464
+
465
+ test("returns unknown for Go without go.mod", async () => {
466
+ const checker = createMockFileChecker({});
467
+ const result = await detectOrm("/test", "go", checker);
468
+ expect(result).toBe("unknown");
469
+ });
470
+
471
+ test("detects tortoise for Python", async () => {
472
+ const checker = createMockFileChecker({
473
+ "pyproject.toml": '[tool.poetry.dependencies]\ntortoise = "^0.20"',
474
+ });
475
+ const result = await detectOrm("/test", "python", checker);
476
+ expect(result).toBe("tortoise");
477
+ });
478
+
479
+ test("returns unknown for Python without ORM", async () => {
480
+ const checker = createMockFileChecker({ "pyproject.toml": "" });
481
+ const result = await detectOrm("/test", "python", checker);
482
+ expect(result).toBe("unknown");
483
+ });
484
+
485
+ test("returns unknown for Python without pyproject.toml", async () => {
486
+ const checker = createMockFileChecker({});
487
+ const result = await detectOrm("/test", "python", checker);
488
+ expect(result).toBe("unknown");
489
+ });
490
+
491
+ test("detects sqlx for Rust", async () => {
492
+ const checker = createMockFileChecker({
493
+ "Cargo.toml": '[dependencies]\nsqlx = "0.7"',
494
+ });
495
+ const result = await detectOrm("/test", "rust", checker);
496
+ expect(result).toBe("sqlx");
497
+ });
498
+
499
+ test("detects sea-orm for Rust", async () => {
500
+ const checker = createMockFileChecker({
501
+ "Cargo.toml": '[dependencies]\nsea-orm = "0.12"',
502
+ });
503
+ const result = await detectOrm("/test", "rust", checker);
504
+ expect(result).toBe("sea-orm");
505
+ });
506
+
507
+ test("returns unknown for Rust without ORM", async () => {
508
+ const checker = createMockFileChecker({ "Cargo.toml": "[dependencies]" });
509
+ const result = await detectOrm("/test", "rust", checker);
510
+ expect(result).toBe("unknown");
511
+ });
512
+
513
+ test("returns unknown for Rust without Cargo.toml", async () => {
514
+ const checker = createMockFileChecker({});
515
+ const result = await detectOrm("/test", "rust", checker);
516
+ expect(result).toBe("unknown");
517
+ });
518
+
519
+ test("detects drizzle for nodejs", async () => {
520
+ const checker = createMockFileChecker({
521
+ "package.json": '{"dependencies": {"drizzle-orm": "^0.29"}}',
522
+ });
523
+ const result = await detectOrm("/test", "nodejs", checker);
524
+ expect(result).toBe("drizzle");
525
+ });
526
+
527
+ test("detects typeorm for nodejs", async () => {
528
+ const checker = createMockFileChecker({
529
+ "package.json": '{"dependencies": {"typeorm": "^0.3"}}',
530
+ });
531
+ const result = await detectOrm("/test", "nodejs", checker);
532
+ expect(result).toBe("typeorm");
533
+ });
534
+
535
+ test("detects sequelize for nodejs", async () => {
536
+ const checker = createMockFileChecker({
537
+ "package.json": '{"dependencies": {"sequelize": "^6"}}',
538
+ });
539
+ const result = await detectOrm("/test", "nodejs", checker);
540
+ expect(result).toBe("sequelize");
541
+ });
542
+
543
+ test("returns unknown for nodejs without package.json", async () => {
544
+ const checker = createMockFileChecker({});
545
+ const result = await detectOrm("/test", "nodejs", checker);
546
+ expect(result).toBe("unknown");
547
+ });
548
+
549
+ test("detects ORM for go-workspace", async () => {
550
+ const checker = createMockFileChecker({ "sqlc.yaml": "" });
551
+ const result = await detectOrm("/test", "go-workspace", checker);
552
+ expect(result).toBe("sqlc");
553
+ });
554
+
555
+ test("detects ORM for nextjs", async () => {
556
+ const checker = createMockFileChecker({
557
+ "package.json": '{"dependencies": {"prisma": "^5"}}',
558
+ });
559
+ const result = await detectOrm("/test", "nextjs", checker);
560
+ expect(result).toBe("prisma");
561
+ });
562
+
563
+ test("detects ORM for react", async () => {
564
+ const checker = createMockFileChecker({
565
+ "package.json": '{"dependencies": {"drizzle-orm": "^0.29"}}',
566
+ });
567
+ const result = await detectOrm("/test", "react", checker);
568
+ expect(result).toBe("drizzle");
569
+ });
570
+
571
+ test("detects ORM for express", async () => {
572
+ const checker = createMockFileChecker({
573
+ "package.json": '{"dependencies": {"sequelize": "^6"}}',
574
+ });
575
+ const result = await detectOrm("/test", "express", checker);
576
+ expect(result).toBe("sequelize");
577
+ });
578
+
579
+ test("returns unknown for unknown project type", async () => {
580
+ const checker = createMockFileChecker({});
581
+ const result = await detectOrm("/test", "unknown", checker);
582
+ expect(result).toBe("unknown");
583
+ });
584
+
585
+ test("returns unknown for terraform", async () => {
586
+ const checker = createMockFileChecker({});
587
+ const result = await detectOrm("/test", "terraform", checker);
588
+ expect(result).toBe("unknown");
589
+ });
590
+ });
591
+
592
+ describe("isMonorepo", () => {
593
+ test("detects Go workspace as monorepo", async () => {
594
+ const checker = createMockFileChecker({ "go.work": "" });
595
+ const result = await isMonorepo("/test", checker);
596
+ expect(result).toBe(true);
597
+ });
598
+
599
+ test("detects npm workspaces as monorepo", async () => {
600
+ const checker = createMockFileChecker({
601
+ "package.json": '{"workspaces": ["packages/*"]}',
602
+ });
603
+ const result = await isMonorepo("/test", checker);
604
+ expect(result).toBe(true);
605
+ });
606
+
607
+ test("detects multiple language files as monorepo", async () => {
608
+ const checker = createMockFileChecker({
609
+ "backend/go.mod": "",
610
+ "frontend/package.json": "",
611
+ });
612
+ const result = await isMonorepo("/test", checker);
613
+ expect(result).toBe(true);
614
+ });
615
+
616
+ test("returns false for single project", async () => {
617
+ const checker = createMockFileChecker({ "go.mod": "" });
618
+ const result = await isMonorepo("/test", checker);
619
+ expect(result).toBe(false);
620
+ });
621
+ });
622
+
623
+ describe("detectWorkspaces", () => {
624
+ test("returns root for single project", async () => {
625
+ const checker = createMockFileChecker({ "go.mod": "" });
626
+ const result = await detectWorkspaces("/test", checker);
627
+ expect(result).toEqual(["/test"]);
628
+ });
629
+
630
+ test("detects multiple workspaces in monorepo", async () => {
631
+ const checker = createMockFileChecker({
632
+ "go.work": "",
633
+ "backend/go.mod": "",
634
+ "frontend/package.json": "",
635
+ });
636
+ const result = await detectWorkspaces("/test", checker);
637
+ expect(result.length).toBeGreaterThan(0);
638
+ });
639
+ });
640
+
641
+ describe("detectArchitecturePattern", () => {
642
+ test("detects go-standard-layout", async () => {
643
+ const checker = createMockFileChecker({ internal: "", cmd: "" });
644
+ const result = await detectArchitecturePattern("/test", checker);
645
+ expect(result).toBe("go-standard-layout");
646
+ });
647
+
648
+ test("detects clean-architecture", async () => {
649
+ const checker = createMockFileChecker({ usecases: "", gateways: "" });
650
+ const result = await detectArchitecturePattern("/test", checker);
651
+ expect(result).toBe("clean-architecture");
652
+ });
653
+
654
+ test("detects ddd-layered", async () => {
655
+ const checker = createMockFileChecker({ domain: "", infrastructure: "" });
656
+ const result = await detectArchitecturePattern("/test", checker);
657
+ expect(result).toBe("ddd-layered");
658
+ });
659
+
660
+ test("detects react-standard", async () => {
661
+ const mockChecker: FileChecker = {
662
+ async exists(filePath: string): Promise<boolean> {
663
+ return filePath.endsWith("src/components");
664
+ },
665
+ async readText(): Promise<string> { return ""; },
666
+ async glob(): Promise<string[]> { return []; },
667
+ };
668
+ const result = await detectArchitecturePattern("/test", mockChecker);
669
+ expect(result).toBe("react-standard");
670
+ });
671
+
672
+ test("returns custom for no recognized pattern", async () => {
673
+ const checker = createMockFileChecker({});
674
+ const result = await detectArchitecturePattern("/test", checker);
675
+ expect(result).toBe("custom");
676
+ });
677
+ });
678
+
679
+ describe("analyzeProject", () => {
680
+ test("analyzes single Go project", async () => {
681
+ const checker = createMockFileChecker({
682
+ "go.mod": "module example",
683
+ "go.sum": "",
684
+ });
685
+ const result = await analyzeProject("/test", checker);
686
+
687
+ expect(result.projectType).toBe("go");
688
+ expect(result.packageManager).toBe("go");
689
+ expect(result.isMonorepo).toBe(false);
690
+ expect(result.workspaces).toHaveLength(1);
691
+ expect(result.workspaces[0]?.path).toBe("/test");
692
+ });
693
+
694
+ test("analyzes Next.js monorepo", async () => {
695
+ const checker = createMockFileChecker({
696
+ "package.json": '{"workspaces": ["apps/*"], "dependencies": {"next": "14.0.0"}}',
697
+ "pnpm-lock.yaml": "",
698
+ "apps/web/package.json": '{"dependencies": {"next": "14.0.0"}}',
699
+ });
700
+ const result = await analyzeProject("/test", checker);
701
+
702
+ expect(result.projectType).toBe("nextjs");
703
+ expect(result.packageManager).toBe("pnpm");
704
+ expect(result.isMonorepo).toBe(true);
705
+ expect(result.framework).toBe("nextjs");
706
+ });
707
+ });