@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.
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/aad.js +2 -0
- package/package.json +78 -0
- package/src/__tests__/e2e/pipeline-e2e.test.ts +279 -0
- package/src/__tests__/e2e/resume-e2e.test.ts +200 -0
- package/src/__tests__/integration/cli-smoke.test.ts +175 -0
- package/src/__tests__/integration/pipeline.test.ts +346 -0
- package/src/bun-imports.d.ts +14 -0
- package/src/main.ts +52 -0
- package/src/modules/claude-provider/__tests__/claude-cli.adapter.test.ts +277 -0
- package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +127 -0
- package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +347 -0
- package/src/modules/claude-provider/__tests__/effort-strategy.test.ts +212 -0
- package/src/modules/claude-provider/__tests__/provider-registry.test.ts +251 -0
- package/src/modules/claude-provider/__tests__/retry.test.ts +201 -0
- package/src/modules/claude-provider/claude-cli.adapter.ts +156 -0
- package/src/modules/claude-provider/claude-provider.port.ts +35 -0
- package/src/modules/claude-provider/claude-sdk.adapter.ts +217 -0
- package/src/modules/claude-provider/effort-strategy.ts +94 -0
- package/src/modules/claude-provider/index.ts +32 -0
- package/src/modules/claude-provider/provider-registry.ts +92 -0
- package/src/modules/claude-provider/retry.ts +81 -0
- package/src/modules/cli/__tests__/app.test.ts +160 -0
- package/src/modules/cli/__tests__/cleanup.test.ts +111 -0
- package/src/modules/cli/__tests__/commands.test.ts +186 -0
- package/src/modules/cli/__tests__/output.test.ts +329 -0
- package/src/modules/cli/__tests__/resume.test.ts +324 -0
- package/src/modules/cli/__tests__/run.test.ts +168 -0
- package/src/modules/cli/__tests__/shutdown.test.ts +168 -0
- package/src/modules/cli/__tests__/status.test.ts +144 -0
- package/src/modules/cli/app.ts +241 -0
- package/src/modules/cli/commands/cleanup.ts +120 -0
- package/src/modules/cli/commands/resume.ts +156 -0
- package/src/modules/cli/commands/run.ts +322 -0
- package/src/modules/cli/commands/status.ts +101 -0
- package/src/modules/cli/index.ts +29 -0
- package/src/modules/cli/output.ts +256 -0
- package/src/modules/cli/shutdown.ts +122 -0
- package/src/modules/dashboard/__tests__/api-routes.test.ts +204 -0
- package/src/modules/dashboard/__tests__/file-watcher.test.ts +34 -0
- package/src/modules/dashboard/__tests__/server.test.ts +120 -0
- package/src/modules/dashboard/__tests__/sse-broadcaster.test.ts +163 -0
- package/src/modules/dashboard/__tests__/sse-routes.test.ts +58 -0
- package/src/modules/dashboard/__tests__/state-aggregator.test.ts +330 -0
- package/src/modules/dashboard/index.ts +8 -0
- package/src/modules/dashboard/routes/api.ts +84 -0
- package/src/modules/dashboard/routes/sse.ts +37 -0
- package/src/modules/dashboard/server.ts +111 -0
- package/src/modules/dashboard/services/file-watcher.ts +36 -0
- package/src/modules/dashboard/services/sse-broadcaster.ts +81 -0
- package/src/modules/dashboard/services/state-aggregator.ts +132 -0
- package/src/modules/dashboard/ui/dashboard.html +405 -0
- package/src/modules/git-workspace/__tests__/branch-manager.test.ts +335 -0
- package/src/modules/git-workspace/__tests__/git-exec.test.ts +91 -0
- package/src/modules/git-workspace/__tests__/memory-sync.test.ts +273 -0
- package/src/modules/git-workspace/__tests__/merge-service.test.ts +286 -0
- package/src/modules/git-workspace/__tests__/settings-merge.test.ts +163 -0
- package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +247 -0
- package/src/modules/git-workspace/branch-manager.ts +191 -0
- package/src/modules/git-workspace/git-exec.ts +124 -0
- package/src/modules/git-workspace/index.ts +17 -0
- package/src/modules/git-workspace/memory-sync.ts +89 -0
- package/src/modules/git-workspace/merge-service.ts +156 -0
- package/src/modules/git-workspace/settings-merge.ts +95 -0
- package/src/modules/git-workspace/worktree-manager.ts +199 -0
- package/src/modules/logging/__tests__/log-store.test.ts +242 -0
- package/src/modules/logging/__tests__/logger.test.ts +81 -0
- package/src/modules/logging/__tests__/sse-transport.test.ts +93 -0
- package/src/modules/logging/index.ts +7 -0
- package/src/modules/logging/log-store.ts +80 -0
- package/src/modules/logging/logger.ts +55 -0
- package/src/modules/logging/transports/sse-transport.ts +28 -0
- package/src/modules/multi-repo/__tests__/multi-repo-planner.test.ts +93 -0
- package/src/modules/multi-repo/__tests__/repo-context.test.ts +79 -0
- package/src/modules/multi-repo/index.ts +12 -0
- package/src/modules/multi-repo/multi-repo-planner.ts +112 -0
- package/src/modules/multi-repo/repo-context.ts +71 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +13 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +10 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +5 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +5 -0
- package/src/modules/persistence/__tests__/file-lock.test.ts +141 -0
- package/src/modules/persistence/__tests__/index.test.ts +38 -0
- package/src/modules/persistence/__tests__/stores.test.ts +594 -0
- package/src/modules/persistence/file-lock.ts +158 -0
- package/src/modules/persistence/fs-run-store.ts +73 -0
- package/src/modules/persistence/fs-task-store.ts +152 -0
- package/src/modules/persistence/fs-worker-store.ts +116 -0
- package/src/modules/persistence/in-memory-stores.ts +98 -0
- package/src/modules/persistence/index.ts +60 -0
- package/src/modules/persistence/stores.port.ts +60 -0
- package/src/modules/planning/__tests__/file-conflict-validator.test.ts +256 -0
- package/src/modules/planning/__tests__/planning-service.test.ts +366 -0
- package/src/modules/planning/__tests__/project-detection.test.ts +707 -0
- package/src/modules/planning/file-conflict-validator.ts +135 -0
- package/src/modules/planning/index.ts +40 -0
- package/src/modules/planning/planning.service.ts +262 -0
- package/src/modules/planning/project-detection.ts +525 -0
- package/src/modules/plugin/__tests__/plugin-loader.test.ts +83 -0
- package/src/modules/plugin/__tests__/plugin-manager.test.ts +187 -0
- package/src/modules/plugin/index.ts +3 -0
- package/src/modules/plugin/plugin-loader.ts +46 -0
- package/src/modules/plugin/plugin-manager.ts +90 -0
- package/src/modules/plugin/plugin.types.ts +37 -0
- package/src/modules/process-manager/__tests__/process-manager.test.ts +210 -0
- package/src/modules/process-manager/__tests__/worker.test.ts +89 -0
- package/src/modules/process-manager/index.ts +5 -0
- package/src/modules/process-manager/process-manager.ts +193 -0
- package/src/modules/process-manager/worker.ts +106 -0
- package/src/modules/task-execution/__tests__/default-spawner.test.ts +154 -0
- package/src/modules/task-execution/__tests__/executor.test.ts +760 -0
- package/src/modules/task-execution/__tests__/implementer-green.test.ts +286 -0
- package/src/modules/task-execution/__tests__/merge-phase.test.ts +368 -0
- package/src/modules/task-execution/__tests__/reviewer.test.ts +302 -0
- package/src/modules/task-execution/__tests__/tester-red.test.ts +281 -0
- package/src/modules/task-execution/__tests__/tester-verify.test.ts +313 -0
- package/src/modules/task-execution/executor.ts +303 -0
- package/src/modules/task-execution/index.ts +45 -0
- package/src/modules/task-execution/phases/default-spawner.ts +49 -0
- package/src/modules/task-execution/phases/implementer-green.ts +100 -0
- package/src/modules/task-execution/phases/merge.ts +122 -0
- package/src/modules/task-execution/phases/reviewer.ts +160 -0
- package/src/modules/task-execution/phases/tester-red.ts +100 -0
- package/src/modules/task-execution/phases/tester-verify.ts +120 -0
- package/src/modules/task-queue/__tests__/dependency-resolver.test.ts +456 -0
- package/src/modules/task-queue/__tests__/dispatcher.test.ts +824 -0
- package/src/modules/task-queue/__tests__/task-plan.test.ts +122 -0
- package/src/modules/task-queue/__tests__/task.test.ts +130 -0
- package/src/modules/task-queue/dependency-resolver.ts +171 -0
- package/src/modules/task-queue/dispatcher.ts +372 -0
- package/src/modules/task-queue/index.ts +16 -0
- package/src/modules/task-queue/task-plan.ts +40 -0
- package/src/modules/task-queue/task.ts +67 -0
- package/src/shared/__tests__/config.test.ts +204 -0
- package/src/shared/__tests__/errors.test.ts +285 -0
- package/src/shared/__tests__/events.test.ts +496 -0
- package/src/shared/__tests__/types.test.ts +360 -0
- package/src/shared/config.ts +133 -0
- package/src/shared/errors.ts +128 -0
- package/src/shared/events.ts +171 -0
- package/src/shared/types.ts +143 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
// Project Detection - port from .aad/scripts/lib/project-detection.sh
|
|
2
|
+
// Detects project type, package manager, framework, test framework, ORM, and architecture patterns
|
|
3
|
+
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
// Type Definitions
|
|
7
|
+
export type ProjectType =
|
|
8
|
+
| "go"
|
|
9
|
+
| "go-workspace"
|
|
10
|
+
| "rust"
|
|
11
|
+
| "python"
|
|
12
|
+
| "nodejs"
|
|
13
|
+
| "nextjs"
|
|
14
|
+
| "express"
|
|
15
|
+
| "react"
|
|
16
|
+
| "terraform"
|
|
17
|
+
| "unknown";
|
|
18
|
+
|
|
19
|
+
export type PackageManager =
|
|
20
|
+
| "uv"
|
|
21
|
+
| "poetry"
|
|
22
|
+
| "pipenv"
|
|
23
|
+
| "pip"
|
|
24
|
+
| "pnpm"
|
|
25
|
+
| "yarn"
|
|
26
|
+
| "npm"
|
|
27
|
+
| "bun"
|
|
28
|
+
| "cargo"
|
|
29
|
+
| "go"
|
|
30
|
+
| "unknown";
|
|
31
|
+
|
|
32
|
+
export type TestFramework =
|
|
33
|
+
| "pytest"
|
|
34
|
+
| "unittest"
|
|
35
|
+
| "vitest"
|
|
36
|
+
| "jest"
|
|
37
|
+
| "bun:test"
|
|
38
|
+
| "mocha"
|
|
39
|
+
| "go-test"
|
|
40
|
+
| "cargo-test"
|
|
41
|
+
| "terraform-validate"
|
|
42
|
+
| "unknown";
|
|
43
|
+
|
|
44
|
+
export type Framework =
|
|
45
|
+
| "nextjs"
|
|
46
|
+
| "express"
|
|
47
|
+
| "react"
|
|
48
|
+
| "fastapi"
|
|
49
|
+
| "django"
|
|
50
|
+
| "flask"
|
|
51
|
+
| "gin"
|
|
52
|
+
| "echo"
|
|
53
|
+
| "actix"
|
|
54
|
+
| "unknown";
|
|
55
|
+
|
|
56
|
+
export type OrmType =
|
|
57
|
+
| "prisma"
|
|
58
|
+
| "drizzle"
|
|
59
|
+
| "typeorm"
|
|
60
|
+
| "sequelize"
|
|
61
|
+
| "sqlalchemy"
|
|
62
|
+
| "tortoise"
|
|
63
|
+
| "gorm"
|
|
64
|
+
| "sqlx"
|
|
65
|
+
| "sqlc"
|
|
66
|
+
| "diesel"
|
|
67
|
+
| "sea-orm"
|
|
68
|
+
| "unknown";
|
|
69
|
+
|
|
70
|
+
export type ArchitecturePattern =
|
|
71
|
+
| "hexagonal"
|
|
72
|
+
| "clean-architecture"
|
|
73
|
+
| "ddd-layered"
|
|
74
|
+
| "go-standard-layout"
|
|
75
|
+
| "react-standard"
|
|
76
|
+
| "custom"
|
|
77
|
+
| "unknown";
|
|
78
|
+
|
|
79
|
+
export interface WorkspaceInfo {
|
|
80
|
+
path: string;
|
|
81
|
+
projectType: ProjectType;
|
|
82
|
+
packageManager: PackageManager;
|
|
83
|
+
testFramework: TestFramework;
|
|
84
|
+
framework?: Framework;
|
|
85
|
+
orm?: OrmType;
|
|
86
|
+
architecture?: ArchitecturePattern;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ProjectAnalysis {
|
|
90
|
+
projectType: ProjectType;
|
|
91
|
+
packageManager: PackageManager;
|
|
92
|
+
isMonorepo: boolean;
|
|
93
|
+
workspaces: WorkspaceInfo[];
|
|
94
|
+
framework?: Framework;
|
|
95
|
+
orm?: OrmType;
|
|
96
|
+
architecture?: ArchitecturePattern;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// FileChecker interface for dependency injection (testability)
|
|
100
|
+
export interface FileChecker {
|
|
101
|
+
exists(path: string): Promise<boolean>;
|
|
102
|
+
readText(path: string): Promise<string>;
|
|
103
|
+
glob(pattern: string, cwd: string): Promise<string[]>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Bun-based FileChecker implementation
|
|
107
|
+
export function createBunFileChecker(): FileChecker {
|
|
108
|
+
return {
|
|
109
|
+
async exists(filePath: string): Promise<boolean> {
|
|
110
|
+
return await Bun.file(filePath).exists();
|
|
111
|
+
},
|
|
112
|
+
async readText(filePath: string): Promise<string> {
|
|
113
|
+
try {
|
|
114
|
+
return await Bun.file(filePath).text();
|
|
115
|
+
} catch {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
async glob(pattern: string, cwd: string): Promise<string[]> {
|
|
120
|
+
const globber = new Bun.Glob(pattern);
|
|
121
|
+
const matches: string[] = [];
|
|
122
|
+
for await (const file of globber.scan({
|
|
123
|
+
cwd,
|
|
124
|
+
absolute: false,
|
|
125
|
+
onlyFiles: true,
|
|
126
|
+
})) {
|
|
127
|
+
// Exclude node_modules, .git, and .aad directories
|
|
128
|
+
if (
|
|
129
|
+
!file.includes("node_modules/") &&
|
|
130
|
+
!file.includes(".git/") &&
|
|
131
|
+
!file.includes(".aad/")
|
|
132
|
+
) {
|
|
133
|
+
matches.push(file);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return matches;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================
|
|
142
|
+
// Project Type Detection
|
|
143
|
+
// ============================================================
|
|
144
|
+
|
|
145
|
+
export async function detectProjectType(
|
|
146
|
+
projectRoot: string,
|
|
147
|
+
checker: FileChecker
|
|
148
|
+
): Promise<ProjectType> {
|
|
149
|
+
// Go
|
|
150
|
+
if (await checker.exists(path.join(projectRoot, "go.work"))) {
|
|
151
|
+
return "go-workspace";
|
|
152
|
+
}
|
|
153
|
+
if (await checker.exists(path.join(projectRoot, "go.mod"))) {
|
|
154
|
+
return "go";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Rust
|
|
158
|
+
if (await checker.exists(path.join(projectRoot, "Cargo.toml"))) {
|
|
159
|
+
return "rust";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Python
|
|
163
|
+
if (await checker.exists(path.join(projectRoot, "pyproject.toml"))) {
|
|
164
|
+
return "python";
|
|
165
|
+
}
|
|
166
|
+
if (await checker.exists(path.join(projectRoot, "requirements.txt"))) {
|
|
167
|
+
return "python";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Node.js/TypeScript
|
|
171
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
172
|
+
if (await checker.exists(packageJsonPath)) {
|
|
173
|
+
const content = await checker.readText(packageJsonPath);
|
|
174
|
+
if (content.includes('"next"')) return "nextjs";
|
|
175
|
+
if (content.includes('"express"')) return "express";
|
|
176
|
+
if (content.includes('"react"')) return "react";
|
|
177
|
+
return "nodejs";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Terraform
|
|
181
|
+
const tfFiles = await checker.glob("*.tf", projectRoot);
|
|
182
|
+
if (tfFiles.length > 0) {
|
|
183
|
+
return "terraform";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return "unknown";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================
|
|
190
|
+
// Package Manager Detection
|
|
191
|
+
// ============================================================
|
|
192
|
+
|
|
193
|
+
export async function detectPackageManager(
|
|
194
|
+
projectRoot: string,
|
|
195
|
+
checker: FileChecker
|
|
196
|
+
): Promise<PackageManager> {
|
|
197
|
+
// Python
|
|
198
|
+
if (await checker.exists(path.join(projectRoot, "uv.lock"))) return "uv";
|
|
199
|
+
if (await checker.exists(path.join(projectRoot, "poetry.lock"))) return "poetry";
|
|
200
|
+
if (await checker.exists(path.join(projectRoot, "Pipfile.lock"))) return "pipenv";
|
|
201
|
+
if (await checker.exists(path.join(projectRoot, "requirements.txt"))) return "pip";
|
|
202
|
+
|
|
203
|
+
// Node.js
|
|
204
|
+
if (await checker.exists(path.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
205
|
+
if (await checker.exists(path.join(projectRoot, "yarn.lock"))) return "yarn";
|
|
206
|
+
if (await checker.exists(path.join(projectRoot, "package-lock.json"))) return "npm";
|
|
207
|
+
if (await checker.exists(path.join(projectRoot, "bun.lockb"))) return "bun";
|
|
208
|
+
|
|
209
|
+
// Rust/Go
|
|
210
|
+
if (await checker.exists(path.join(projectRoot, "Cargo.lock"))) return "cargo";
|
|
211
|
+
if (await checker.exists(path.join(projectRoot, "go.sum"))) return "go";
|
|
212
|
+
|
|
213
|
+
return "unknown";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================================================
|
|
217
|
+
// Framework Detection
|
|
218
|
+
// ============================================================
|
|
219
|
+
|
|
220
|
+
export async function detectFramework(
|
|
221
|
+
projectRoot: string,
|
|
222
|
+
projectType: ProjectType,
|
|
223
|
+
checker: FileChecker
|
|
224
|
+
): Promise<Framework> {
|
|
225
|
+
switch (projectType) {
|
|
226
|
+
case "python": {
|
|
227
|
+
const pyprojectPath = path.join(projectRoot, "pyproject.toml");
|
|
228
|
+
if (await checker.exists(pyprojectPath)) {
|
|
229
|
+
const content = await checker.readText(pyprojectPath);
|
|
230
|
+
if (content.includes("fastapi")) return "fastapi";
|
|
231
|
+
if (content.includes("django")) return "django";
|
|
232
|
+
if (content.includes("flask")) return "flask";
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "rust": {
|
|
237
|
+
const cargoPath = path.join(projectRoot, "Cargo.toml");
|
|
238
|
+
if (await checker.exists(cargoPath)) {
|
|
239
|
+
const content = await checker.readText(cargoPath);
|
|
240
|
+
if (content.includes("actix")) return "actix";
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case "go":
|
|
245
|
+
case "go-workspace":
|
|
246
|
+
return "unknown"; // Go typically doesn't use heavy frameworks
|
|
247
|
+
case "nextjs":
|
|
248
|
+
return "nextjs";
|
|
249
|
+
case "express":
|
|
250
|
+
return "express";
|
|
251
|
+
case "react":
|
|
252
|
+
return "react";
|
|
253
|
+
}
|
|
254
|
+
return "unknown";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================================
|
|
258
|
+
// Test Framework Detection
|
|
259
|
+
// ============================================================
|
|
260
|
+
|
|
261
|
+
export async function detectTestFramework(
|
|
262
|
+
projectRoot: string,
|
|
263
|
+
projectType: ProjectType,
|
|
264
|
+
checker: FileChecker
|
|
265
|
+
): Promise<TestFramework> {
|
|
266
|
+
switch (projectType) {
|
|
267
|
+
case "go":
|
|
268
|
+
case "go-workspace":
|
|
269
|
+
return "go-test";
|
|
270
|
+
|
|
271
|
+
case "rust":
|
|
272
|
+
return "cargo-test";
|
|
273
|
+
|
|
274
|
+
case "python": {
|
|
275
|
+
const pyprojectPath = path.join(projectRoot, "pyproject.toml");
|
|
276
|
+
if (await checker.exists(pyprojectPath)) {
|
|
277
|
+
const content = await checker.readText(pyprojectPath);
|
|
278
|
+
if (content.includes("pytest")) return "pytest";
|
|
279
|
+
}
|
|
280
|
+
return "unittest";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
case "nextjs":
|
|
284
|
+
case "react":
|
|
285
|
+
case "express":
|
|
286
|
+
case "nodejs": {
|
|
287
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
288
|
+
if (await checker.exists(packageJsonPath)) {
|
|
289
|
+
const content = await checker.readText(packageJsonPath);
|
|
290
|
+
if (content.includes('"bun-types"') || content.includes('"@types/bun"')) {
|
|
291
|
+
return "bun:test";
|
|
292
|
+
}
|
|
293
|
+
if (content.includes('"vitest"')) return "vitest";
|
|
294
|
+
if (content.includes('"jest"')) return "jest";
|
|
295
|
+
if (content.includes('"mocha"')) return "mocha";
|
|
296
|
+
}
|
|
297
|
+
return "unknown";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
case "terraform":
|
|
301
|
+
return "terraform-validate";
|
|
302
|
+
|
|
303
|
+
default:
|
|
304
|
+
return "unknown";
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ============================================================
|
|
309
|
+
// ORM Detection
|
|
310
|
+
// ============================================================
|
|
311
|
+
|
|
312
|
+
export async function detectOrm(
|
|
313
|
+
projectRoot: string,
|
|
314
|
+
projectType: ProjectType,
|
|
315
|
+
checker: FileChecker
|
|
316
|
+
): Promise<OrmType> {
|
|
317
|
+
switch (projectType) {
|
|
318
|
+
case "go":
|
|
319
|
+
case "go-workspace": {
|
|
320
|
+
if (await checker.exists(path.join(projectRoot, "sqlc.yaml"))) return "sqlc";
|
|
321
|
+
const goModPath = path.join(projectRoot, "go.mod");
|
|
322
|
+
if (await checker.exists(goModPath)) {
|
|
323
|
+
const content = await checker.readText(goModPath);
|
|
324
|
+
if (content.includes("gorm.io/gorm")) return "gorm";
|
|
325
|
+
if (content.includes("jmoiron/sqlx")) return "sqlx";
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
case "python": {
|
|
331
|
+
const pyprojectPath = path.join(projectRoot, "pyproject.toml");
|
|
332
|
+
if (await checker.exists(pyprojectPath)) {
|
|
333
|
+
const content = await checker.readText(pyprojectPath);
|
|
334
|
+
if (content.includes("sqlalchemy")) return "sqlalchemy";
|
|
335
|
+
if (content.includes("tortoise")) return "tortoise";
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
case "rust": {
|
|
341
|
+
const cargoPath = path.join(projectRoot, "Cargo.toml");
|
|
342
|
+
if (await checker.exists(cargoPath)) {
|
|
343
|
+
const content = await checker.readText(cargoPath);
|
|
344
|
+
if (content.includes("sqlx")) return "sqlx";
|
|
345
|
+
if (content.includes("diesel")) return "diesel";
|
|
346
|
+
if (content.includes("sea-orm")) return "sea-orm";
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
case "nextjs":
|
|
352
|
+
case "react":
|
|
353
|
+
case "express":
|
|
354
|
+
case "nodejs": {
|
|
355
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
356
|
+
if (await checker.exists(packageJsonPath)) {
|
|
357
|
+
const content = await checker.readText(packageJsonPath);
|
|
358
|
+
if (content.includes('"prisma"')) return "prisma";
|
|
359
|
+
if (content.includes('"drizzle-orm"')) return "drizzle";
|
|
360
|
+
if (content.includes('"typeorm"')) return "typeorm";
|
|
361
|
+
if (content.includes('"sequelize"')) return "sequelize";
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return "unknown";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ============================================================
|
|
370
|
+
// Architecture Pattern Detection
|
|
371
|
+
// ============================================================
|
|
372
|
+
|
|
373
|
+
export async function detectArchitecturePattern(
|
|
374
|
+
projectRoot: string,
|
|
375
|
+
checker: FileChecker
|
|
376
|
+
): Promise<ArchitecturePattern> {
|
|
377
|
+
// Go standard layout
|
|
378
|
+
if (
|
|
379
|
+
(await checker.exists(path.join(projectRoot, "internal"))) &&
|
|
380
|
+
(await checker.exists(path.join(projectRoot, "cmd")))
|
|
381
|
+
) {
|
|
382
|
+
return "go-standard-layout";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Clean Architecture
|
|
386
|
+
if (
|
|
387
|
+
(await checker.exists(path.join(projectRoot, "usecases"))) &&
|
|
388
|
+
(await checker.exists(path.join(projectRoot, "gateways")))
|
|
389
|
+
) {
|
|
390
|
+
return "clean-architecture";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// DDD Layered
|
|
394
|
+
if (
|
|
395
|
+
(await checker.exists(path.join(projectRoot, "domain"))) &&
|
|
396
|
+
(await checker.exists(path.join(projectRoot, "infrastructure")))
|
|
397
|
+
) {
|
|
398
|
+
return "ddd-layered";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// React standard
|
|
402
|
+
if (await checker.exists(path.join(projectRoot, "src/components"))) {
|
|
403
|
+
return "react-standard";
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return "custom";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ============================================================
|
|
410
|
+
// Monorepo Detection
|
|
411
|
+
// ============================================================
|
|
412
|
+
|
|
413
|
+
export async function isMonorepo(
|
|
414
|
+
projectRoot: string,
|
|
415
|
+
checker: FileChecker
|
|
416
|
+
): Promise<boolean> {
|
|
417
|
+
// Go workspace
|
|
418
|
+
if (await checker.exists(path.join(projectRoot, "go.work"))) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// npm/yarn/pnpm workspaces
|
|
423
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
424
|
+
if (await checker.exists(packageJsonPath)) {
|
|
425
|
+
const content = await checker.readText(packageJsonPath);
|
|
426
|
+
if (content.includes('"workspaces"')) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Multiple language files in subdirectories
|
|
432
|
+
const patterns = ["**/go.mod", "**/package.json", "**/Cargo.toml", "**/pyproject.toml"];
|
|
433
|
+
let totalCount = 0;
|
|
434
|
+
|
|
435
|
+
for (const pattern of patterns) {
|
|
436
|
+
const matches = await checker.glob(pattern, projectRoot);
|
|
437
|
+
totalCount += matches.length;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return totalCount > 1;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ============================================================
|
|
444
|
+
// Workspace Detection
|
|
445
|
+
// ============================================================
|
|
446
|
+
|
|
447
|
+
export async function detectWorkspaces(
|
|
448
|
+
projectRoot: string,
|
|
449
|
+
checker: FileChecker
|
|
450
|
+
): Promise<string[]> {
|
|
451
|
+
if (!(await isMonorepo(projectRoot, checker))) {
|
|
452
|
+
// Single project: return root only
|
|
453
|
+
return [projectRoot];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const workspaces = new Set<string>();
|
|
457
|
+
workspaces.add(projectRoot);
|
|
458
|
+
|
|
459
|
+
// Scan for language-specific project files
|
|
460
|
+
const patterns = [
|
|
461
|
+
{ pattern: "**/go.mod", maxDepth: 3 },
|
|
462
|
+
{ pattern: "**/package.json", maxDepth: 3 },
|
|
463
|
+
{ pattern: "**/Cargo.toml", maxDepth: 3 },
|
|
464
|
+
{ pattern: "**/pyproject.toml", maxDepth: 3 },
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
for (const { pattern } of patterns) {
|
|
468
|
+
const matches = await checker.glob(pattern, projectRoot);
|
|
469
|
+
for (const match of matches) {
|
|
470
|
+
const dir = path.dirname(path.join(projectRoot, match));
|
|
471
|
+
workspaces.add(dir);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return Array.from(workspaces).sort();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ============================================================
|
|
479
|
+
// Full Project Analysis
|
|
480
|
+
// ============================================================
|
|
481
|
+
|
|
482
|
+
export async function analyzeProject(
|
|
483
|
+
projectRoot: string,
|
|
484
|
+
checker: FileChecker = createBunFileChecker()
|
|
485
|
+
): Promise<ProjectAnalysis> {
|
|
486
|
+
const projectType = await detectProjectType(projectRoot, checker);
|
|
487
|
+
const packageManager = await detectPackageManager(projectRoot, checker);
|
|
488
|
+
const mono = await isMonorepo(projectRoot, checker);
|
|
489
|
+
const workspacePaths = await detectWorkspaces(projectRoot, checker);
|
|
490
|
+
|
|
491
|
+
const framework = await detectFramework(projectRoot, projectType, checker);
|
|
492
|
+
const orm = await detectOrm(projectRoot, projectType, checker);
|
|
493
|
+
const architecture = await detectArchitecturePattern(projectRoot, checker);
|
|
494
|
+
|
|
495
|
+
const workspaces: WorkspaceInfo[] = await Promise.all(
|
|
496
|
+
workspacePaths.map(async (wsPath) => {
|
|
497
|
+
const wsType = await detectProjectType(wsPath, checker);
|
|
498
|
+
const wsPkgMgr = await detectPackageManager(wsPath, checker);
|
|
499
|
+
const wsTestFw = await detectTestFramework(wsPath, wsType, checker);
|
|
500
|
+
const wsFw = await detectFramework(wsPath, wsType, checker);
|
|
501
|
+
const wsOrm = await detectOrm(wsPath, wsType, checker);
|
|
502
|
+
const wsArch = await detectArchitecturePattern(wsPath, checker);
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
path: wsPath,
|
|
506
|
+
projectType: wsType,
|
|
507
|
+
packageManager: wsPkgMgr,
|
|
508
|
+
testFramework: wsTestFw,
|
|
509
|
+
framework: wsFw !== "unknown" ? wsFw : undefined,
|
|
510
|
+
orm: wsOrm !== "unknown" ? wsOrm : undefined,
|
|
511
|
+
architecture: wsArch !== "custom" ? wsArch : undefined,
|
|
512
|
+
};
|
|
513
|
+
})
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
projectType,
|
|
518
|
+
packageManager,
|
|
519
|
+
isMonorepo: mono,
|
|
520
|
+
workspaces,
|
|
521
|
+
framework: framework !== "unknown" ? framework : undefined,
|
|
522
|
+
orm: orm !== "unknown" ? orm : undefined,
|
|
523
|
+
architecture: architecture !== "custom" ? architecture : undefined,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { loadPlugin } from "../plugin-loader";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const fixturesDir = join(import.meta.dir, "__fixtures__");
|
|
7
|
+
|
|
8
|
+
function setupFixtures() {
|
|
9
|
+
mkdirSync(fixturesDir, { recursive: true });
|
|
10
|
+
|
|
11
|
+
// Valid plugin
|
|
12
|
+
writeFileSync(
|
|
13
|
+
join(fixturesDir, "valid-plugin.ts"),
|
|
14
|
+
`export default {
|
|
15
|
+
name: "valid-plugin",
|
|
16
|
+
version: "1.0.0",
|
|
17
|
+
activate() {},
|
|
18
|
+
deactivate() {},
|
|
19
|
+
};`
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Invalid plugin (missing activate)
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(fixturesDir, "invalid-plugin.ts"),
|
|
25
|
+
`export default {
|
|
26
|
+
name: "invalid",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
};`
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// Invalid plugin (missing name)
|
|
32
|
+
writeFileSync(
|
|
33
|
+
join(fixturesDir, "no-name-plugin.ts"),
|
|
34
|
+
`export default {
|
|
35
|
+
version: "1.0.0",
|
|
36
|
+
activate() {},
|
|
37
|
+
};`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cleanupFixtures() {
|
|
42
|
+
try {
|
|
43
|
+
rmSync(fixturesDir, { recursive: true, force: true });
|
|
44
|
+
} catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("loadPlugin", () => {
|
|
50
|
+
test("loads a valid plugin from absolute path", async () => {
|
|
51
|
+
setupFixtures();
|
|
52
|
+
try {
|
|
53
|
+
const plugin = await loadPlugin(join(fixturesDir, "valid-plugin.ts"));
|
|
54
|
+
expect(plugin.name).toBe("valid-plugin");
|
|
55
|
+
expect(plugin.version).toBe("1.0.0");
|
|
56
|
+
expect(typeof plugin.activate).toBe("function");
|
|
57
|
+
} finally {
|
|
58
|
+
cleanupFixtures();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("rejects invalid plugin (missing activate)", async () => {
|
|
63
|
+
setupFixtures();
|
|
64
|
+
try {
|
|
65
|
+
await expect(loadPlugin(join(fixturesDir, "invalid-plugin.ts"))).rejects.toThrow("Invalid plugin");
|
|
66
|
+
} finally {
|
|
67
|
+
cleanupFixtures();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("rejects invalid plugin (missing name)", async () => {
|
|
72
|
+
setupFixtures();
|
|
73
|
+
try {
|
|
74
|
+
await expect(loadPlugin(join(fixturesDir, "no-name-plugin.ts"))).rejects.toThrow("Invalid plugin");
|
|
75
|
+
} finally {
|
|
76
|
+
cleanupFixtures();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("rejects non-existent path", async () => {
|
|
81
|
+
await expect(loadPlugin("/nonexistent/path/plugin.ts")).rejects.toThrow("Failed to load plugin");
|
|
82
|
+
});
|
|
83
|
+
});
|