@anatolykoptev/krolik-cli 0.1.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/README.md +479 -0
- package/dist/bin/cli.js +32586 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/chunk-DTDOBWO6.js +2061 -0
- package/dist/chunk-DTDOBWO6.js.map +1 -0
- package/dist/config/index.d.ts +115 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +417 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/loader-_tD59b9E.d.ts +157 -0
- package/package.json +118 -0
|
@@ -0,0 +1,2061 @@
|
|
|
1
|
+
import * as fs6 from 'fs';
|
|
2
|
+
import * as path12 from 'path';
|
|
3
|
+
import 'os';
|
|
4
|
+
import { SyntaxKind } from 'ts-morph';
|
|
5
|
+
import { execSync, spawnSync } from 'child_process';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { cosmiconfig } from 'cosmiconfig';
|
|
9
|
+
|
|
10
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
11
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
12
|
+
}) : x)(function(x) {
|
|
13
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
14
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// src/config/defaults.ts
|
|
18
|
+
var DEFAULT_PATHS = {
|
|
19
|
+
web: "src",
|
|
20
|
+
api: "src/api",
|
|
21
|
+
db: "prisma",
|
|
22
|
+
shared: "src/shared",
|
|
23
|
+
components: "src/components",
|
|
24
|
+
hooks: "src/hooks",
|
|
25
|
+
lib: "src/lib"
|
|
26
|
+
};
|
|
27
|
+
var DEFAULT_FEATURES = {
|
|
28
|
+
prisma: false,
|
|
29
|
+
trpc: false,
|
|
30
|
+
nextjs: false,
|
|
31
|
+
react: true,
|
|
32
|
+
monorepo: false,
|
|
33
|
+
typescript: true
|
|
34
|
+
};
|
|
35
|
+
var DEFAULT_PRISMA = {
|
|
36
|
+
schemaDir: "prisma",
|
|
37
|
+
migrationsDir: "prisma/migrations"
|
|
38
|
+
};
|
|
39
|
+
var DEFAULT_TRPC = {
|
|
40
|
+
routersDir: "src/server/routers",
|
|
41
|
+
appRouter: "src/server/routers/index.ts"
|
|
42
|
+
};
|
|
43
|
+
var DEFAULT_TEMPLATES = {
|
|
44
|
+
hook: "",
|
|
45
|
+
component: "",
|
|
46
|
+
test: "",
|
|
47
|
+
schema: ""
|
|
48
|
+
};
|
|
49
|
+
var DEFAULT_EXCLUDE = [
|
|
50
|
+
"node_modules",
|
|
51
|
+
".next",
|
|
52
|
+
"dist",
|
|
53
|
+
"build",
|
|
54
|
+
".git",
|
|
55
|
+
".turbo",
|
|
56
|
+
"coverage",
|
|
57
|
+
".cache"
|
|
58
|
+
];
|
|
59
|
+
var DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
|
60
|
+
function createDefaultConfig(projectRoot) {
|
|
61
|
+
return {
|
|
62
|
+
name: "project",
|
|
63
|
+
projectRoot,
|
|
64
|
+
paths: { ...DEFAULT_PATHS },
|
|
65
|
+
features: { ...DEFAULT_FEATURES },
|
|
66
|
+
prisma: { ...DEFAULT_PRISMA },
|
|
67
|
+
trpc: { ...DEFAULT_TRPC },
|
|
68
|
+
templates: { ...DEFAULT_TEMPLATES },
|
|
69
|
+
exclude: [...DEFAULT_EXCLUDE],
|
|
70
|
+
extensions: [...DEFAULT_EXTENSIONS]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
var FileCache = class {
|
|
74
|
+
cache = /* @__PURE__ */ new Map();
|
|
75
|
+
stats = { hits: 0, misses: 0 };
|
|
76
|
+
options;
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.options = {
|
|
79
|
+
trackMtime: options.trackMtime ?? true,
|
|
80
|
+
collectStats: options.collectStats ?? true
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get file content, reading from disk if not cached
|
|
85
|
+
*
|
|
86
|
+
* Automatically invalidates cache if file modification time changed.
|
|
87
|
+
*
|
|
88
|
+
* @param filepath - Absolute or relative file path
|
|
89
|
+
* @returns File content
|
|
90
|
+
* @throws Error if file cannot be read
|
|
91
|
+
*/
|
|
92
|
+
get(filepath) {
|
|
93
|
+
const absolute = path12.resolve(filepath);
|
|
94
|
+
const cached = this.cache.get(absolute);
|
|
95
|
+
if (cached) {
|
|
96
|
+
if (this.options.trackMtime) {
|
|
97
|
+
try {
|
|
98
|
+
const stat = fs6.statSync(absolute);
|
|
99
|
+
const currentMtime = stat.mtime.getTime();
|
|
100
|
+
if (currentMtime === cached.mtime) {
|
|
101
|
+
if (this.options.collectStats) {
|
|
102
|
+
this.stats.hits++;
|
|
103
|
+
}
|
|
104
|
+
return cached.content;
|
|
105
|
+
}
|
|
106
|
+
this.cache.delete(absolute);
|
|
107
|
+
} catch {
|
|
108
|
+
this.cache.delete(absolute);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
if (this.options.collectStats) {
|
|
112
|
+
this.stats.hits++;
|
|
113
|
+
}
|
|
114
|
+
return cached.content;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (this.options.collectStats) {
|
|
118
|
+
this.stats.misses++;
|
|
119
|
+
}
|
|
120
|
+
const content = fs6.readFileSync(absolute, "utf-8");
|
|
121
|
+
if (this.options.trackMtime) {
|
|
122
|
+
try {
|
|
123
|
+
const stat = fs6.statSync(absolute);
|
|
124
|
+
this.cache.set(absolute, {
|
|
125
|
+
content,
|
|
126
|
+
mtime: stat.mtime.getTime()
|
|
127
|
+
});
|
|
128
|
+
} catch {
|
|
129
|
+
this.cache.set(absolute, {
|
|
130
|
+
content,
|
|
131
|
+
mtime: 0
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
this.cache.set(absolute, {
|
|
136
|
+
content,
|
|
137
|
+
mtime: 0
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return content;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Update cached content (e.g., after fix applied)
|
|
144
|
+
*
|
|
145
|
+
* This keeps the cache consistent with disk state when you
|
|
146
|
+
* write to a file and want to avoid re-reading it.
|
|
147
|
+
*
|
|
148
|
+
* @param filepath - Absolute or relative file path
|
|
149
|
+
* @param content - New file content
|
|
150
|
+
*/
|
|
151
|
+
set(filepath, content) {
|
|
152
|
+
const absolute = path12.resolve(filepath);
|
|
153
|
+
if (this.options.trackMtime) {
|
|
154
|
+
try {
|
|
155
|
+
const stat = fs6.statSync(absolute);
|
|
156
|
+
this.cache.set(absolute, {
|
|
157
|
+
content,
|
|
158
|
+
mtime: stat.mtime.getTime()
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
this.cache.set(absolute, {
|
|
162
|
+
content,
|
|
163
|
+
mtime: 0
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
this.cache.set(absolute, {
|
|
168
|
+
content,
|
|
169
|
+
mtime: 0
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if file is cached (and valid if mtime tracking enabled)
|
|
175
|
+
*
|
|
176
|
+
* @param filepath - Absolute or relative file path
|
|
177
|
+
* @returns true if file is cached and valid
|
|
178
|
+
*/
|
|
179
|
+
has(filepath) {
|
|
180
|
+
const absolute = path12.resolve(filepath);
|
|
181
|
+
const cached = this.cache.has(absolute);
|
|
182
|
+
if (!cached || !this.options.trackMtime) {
|
|
183
|
+
return cached;
|
|
184
|
+
}
|
|
185
|
+
const entry = this.cache.get(absolute);
|
|
186
|
+
if (!entry) return false;
|
|
187
|
+
try {
|
|
188
|
+
const stat = fs6.statSync(absolute);
|
|
189
|
+
if (stat.mtime.getTime() !== entry.mtime) {
|
|
190
|
+
this.cache.delete(absolute);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
} catch {
|
|
195
|
+
this.cache.delete(absolute);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Invalidate cache for a specific file
|
|
201
|
+
*
|
|
202
|
+
* Use when file is modified externally and you want to force
|
|
203
|
+
* a fresh read on next get().
|
|
204
|
+
*
|
|
205
|
+
* @param filepath - Absolute or relative file path
|
|
206
|
+
*/
|
|
207
|
+
invalidate(filepath) {
|
|
208
|
+
const absolute = path12.resolve(filepath);
|
|
209
|
+
this.cache.delete(absolute);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Clear entire cache and reset statistics
|
|
213
|
+
*
|
|
214
|
+
* Call at end of command execution to free memory.
|
|
215
|
+
*/
|
|
216
|
+
clear() {
|
|
217
|
+
this.cache.clear();
|
|
218
|
+
this.stats = { hits: 0, misses: 0 };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get cache statistics
|
|
222
|
+
*
|
|
223
|
+
* @returns Statistics object with hits, misses, size, and memory usage
|
|
224
|
+
*/
|
|
225
|
+
getStats() {
|
|
226
|
+
let memoryBytes = 0;
|
|
227
|
+
for (const entry of this.cache.values()) {
|
|
228
|
+
memoryBytes += entry.content.length * 2;
|
|
229
|
+
memoryBytes += 8;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
hits: this.stats.hits,
|
|
233
|
+
misses: this.stats.misses,
|
|
234
|
+
size: this.cache.size,
|
|
235
|
+
memoryBytes
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Pre-warm cache with multiple files
|
|
240
|
+
*
|
|
241
|
+
* Useful when you know which files will be accessed and want to
|
|
242
|
+
* batch-load them for better performance.
|
|
243
|
+
*
|
|
244
|
+
* @param filepaths - Array of file paths to pre-load
|
|
245
|
+
*/
|
|
246
|
+
warmup(filepaths) {
|
|
247
|
+
for (const filepath of filepaths) {
|
|
248
|
+
try {
|
|
249
|
+
this.get(filepath);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
new FileCache();
|
|
256
|
+
var SKIP_FOLDERS = /* @__PURE__ */ new Set([
|
|
257
|
+
"node_modules",
|
|
258
|
+
".git",
|
|
259
|
+
".next",
|
|
260
|
+
".turbo",
|
|
261
|
+
"dist",
|
|
262
|
+
"build",
|
|
263
|
+
".krolik",
|
|
264
|
+
".claude"
|
|
265
|
+
]);
|
|
266
|
+
function isProject(dir) {
|
|
267
|
+
const hasPackageJson = fs6.existsSync(path12.join(dir, "package.json"));
|
|
268
|
+
const hasGit = fs6.existsSync(path12.join(dir, ".git"));
|
|
269
|
+
const hasTsConfig = fs6.existsSync(path12.join(dir, "tsconfig.json"));
|
|
270
|
+
if (!hasPackageJson && !hasGit) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
let description;
|
|
274
|
+
if (hasPackageJson) {
|
|
275
|
+
try {
|
|
276
|
+
const pkg = JSON.parse(fs6.readFileSync(path12.join(dir, "package.json"), "utf-8"));
|
|
277
|
+
description = pkg.description || pkg.name;
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
name: path12.basename(dir),
|
|
283
|
+
path: dir,
|
|
284
|
+
hasPackageJson,
|
|
285
|
+
hasGit,
|
|
286
|
+
hasTsConfig,
|
|
287
|
+
...description ? { description } : {}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function scanForProjects(workspaceRoot) {
|
|
291
|
+
const projects = [];
|
|
292
|
+
try {
|
|
293
|
+
const entries = fs6.readdirSync(workspaceRoot, { withFileTypes: true });
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
if (!entry.isDirectory() || SKIP_FOLDERS.has(entry.name) || entry.name.startsWith(".")) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const projectPath = path12.join(workspaceRoot, entry.name);
|
|
299
|
+
const project = isProject(projectPath);
|
|
300
|
+
if (project) {
|
|
301
|
+
projects.push(project);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
return projects;
|
|
307
|
+
}
|
|
308
|
+
function detectProjects(workspaceRoot, requestedProject) {
|
|
309
|
+
const rootProject = isProject(workspaceRoot);
|
|
310
|
+
if (requestedProject) {
|
|
311
|
+
const projectPath = path12.join(workspaceRoot, requestedProject);
|
|
312
|
+
if (fs6.existsSync(projectPath)) {
|
|
313
|
+
const project = isProject(projectPath);
|
|
314
|
+
if (project) {
|
|
315
|
+
return { status: "single", project };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (requestedProject === path12.basename(workspaceRoot) && rootProject) {
|
|
319
|
+
return { status: "single", project: rootProject };
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
status: "none",
|
|
323
|
+
message: `Project "${requestedProject}" not found. Use the tool without project parameter to see available projects.`
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (rootProject) {
|
|
327
|
+
return { status: "single", project: rootProject };
|
|
328
|
+
}
|
|
329
|
+
const projects = scanForProjects(workspaceRoot);
|
|
330
|
+
if (projects.length === 0) {
|
|
331
|
+
return {
|
|
332
|
+
status: "none",
|
|
333
|
+
message: "No projects found. Make sure you are in a directory with package.json or subdirectories containing projects."
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (projects.length === 1 && projects[0]) {
|
|
337
|
+
return { status: "single", project: projects[0] };
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
status: "multiple",
|
|
341
|
+
projects,
|
|
342
|
+
message: "Multiple projects found. Please specify which project to analyze."
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function formatProjectList(projects) {
|
|
346
|
+
const lines = [
|
|
347
|
+
"<available-projects>",
|
|
348
|
+
' <instruction>Multiple projects detected. Please call the tool again with the "project" parameter set to one of these:</instruction>',
|
|
349
|
+
""
|
|
350
|
+
];
|
|
351
|
+
for (const p of projects) {
|
|
352
|
+
lines.push(` <project name="${p.name}">`);
|
|
353
|
+
if (p.description) {
|
|
354
|
+
lines.push(` <description>${p.description}</description>`);
|
|
355
|
+
}
|
|
356
|
+
lines.push(` <path>${p.path}</path>`);
|
|
357
|
+
const features = [];
|
|
358
|
+
if (p.hasPackageJson) features.push("npm");
|
|
359
|
+
if (p.hasGit) features.push("git");
|
|
360
|
+
if (p.hasTsConfig) features.push("typescript");
|
|
361
|
+
lines.push(` <features>${features.join(", ")}</features>`);
|
|
362
|
+
lines.push(" </project>");
|
|
363
|
+
}
|
|
364
|
+
lines.push("</available-projects>");
|
|
365
|
+
return lines.join("\n");
|
|
366
|
+
}
|
|
367
|
+
function resolveProjectPath(workspaceRoot, requestedProject) {
|
|
368
|
+
const result = detectProjects(workspaceRoot, requestedProject);
|
|
369
|
+
switch (result.status) {
|
|
370
|
+
case "single":
|
|
371
|
+
if (!result.project) {
|
|
372
|
+
return { error: "Project not found" };
|
|
373
|
+
}
|
|
374
|
+
return { path: result.project.path };
|
|
375
|
+
case "multiple":
|
|
376
|
+
if (!result.projects) {
|
|
377
|
+
return { error: "No projects found" };
|
|
378
|
+
}
|
|
379
|
+
return { error: formatProjectList(result.projects) };
|
|
380
|
+
case "none":
|
|
381
|
+
return { error: result.message || "No projects found" };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function withProjectDetection(args, workspaceRoot, handler) {
|
|
385
|
+
const projectArg = typeof args.project === "string" ? args.project : void 0;
|
|
386
|
+
const resolved = resolveProjectPath(workspaceRoot, projectArg);
|
|
387
|
+
if ("error" in resolved) {
|
|
388
|
+
return resolved.error;
|
|
389
|
+
}
|
|
390
|
+
return handler(resolved.path);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/mcp/tools/core/registry.ts
|
|
394
|
+
var toolRegistry = [];
|
|
395
|
+
function registerTool(tool) {
|
|
396
|
+
if (toolRegistry.some((t) => t.name === tool.name)) {
|
|
397
|
+
throw new Error(`Tool "${tool.name}" is already registered`);
|
|
398
|
+
}
|
|
399
|
+
toolRegistry.push(tool);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/mcp/tools/core/shared.ts
|
|
403
|
+
var PROJECT_PROPERTY = {
|
|
404
|
+
project: {
|
|
405
|
+
type: "string",
|
|
406
|
+
description: 'Project folder name to analyze (e.g., "my-app", "my-project"). If not specified, auto-detects or returns list of available projects.'
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
var FIX_CATEGORIES = ["lint", "type-safety", "complexity", "hardcoded", "srp"];
|
|
410
|
+
var COMMON_FLAGS = {
|
|
411
|
+
path: { flag: "--path", sanitize: "feature" },
|
|
412
|
+
feature: { flag: "--feature", sanitize: "feature" },
|
|
413
|
+
issue: { flag: "--issue", sanitize: "issue" },
|
|
414
|
+
pr: { flag: "--pr", sanitize: "issue" },
|
|
415
|
+
staged: { flag: "--staged" },
|
|
416
|
+
dryRun: { flag: "--dry-run" },
|
|
417
|
+
json: { flag: "--json" },
|
|
418
|
+
apply: { flag: "--apply" },
|
|
419
|
+
safe: { flag: "--safe" }
|
|
420
|
+
};
|
|
421
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
422
|
+
var __dirname2 = path12.dirname(__filename2);
|
|
423
|
+
var CLI_PATH = path12.resolve(__dirname2, "cli.js");
|
|
424
|
+
var TIMEOUT_60S = 6e4;
|
|
425
|
+
var TIMEOUT_120S = 12e4;
|
|
426
|
+
function sanitizeFeatureName(input) {
|
|
427
|
+
if (typeof input !== "string") return null;
|
|
428
|
+
const sanitized = input.trim();
|
|
429
|
+
if (sanitized.length === 0 || sanitized.length > 100) return null;
|
|
430
|
+
if (!/^[a-zA-Z0-9_\-.\s]+$/.test(sanitized)) return null;
|
|
431
|
+
return sanitized;
|
|
432
|
+
}
|
|
433
|
+
function sanitizeIssueNumber(input) {
|
|
434
|
+
if (typeof input === "number") {
|
|
435
|
+
if (Number.isInteger(input) && input > 0 && input < 1e6) return input;
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
if (typeof input === "string") {
|
|
439
|
+
const num = parseInt(input, 10);
|
|
440
|
+
if (Number.isNaN(num) || num <= 0 || num >= 1e6) return null;
|
|
441
|
+
if (!/^\d+$/.test(input.trim())) return null;
|
|
442
|
+
return num;
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
function escapeShellArg(arg) {
|
|
447
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
448
|
+
}
|
|
449
|
+
function parseCommandArgs(command) {
|
|
450
|
+
const args = [];
|
|
451
|
+
let current = "";
|
|
452
|
+
let inQuotes = false;
|
|
453
|
+
let quoteChar = "";
|
|
454
|
+
for (let i = 0; i < command.length; i++) {
|
|
455
|
+
const char = command[i];
|
|
456
|
+
const nextChar = command[i + 1];
|
|
457
|
+
if (char === "\\" && nextChar) {
|
|
458
|
+
current += nextChar;
|
|
459
|
+
i++;
|
|
460
|
+
} else if ((char === '"' || char === "'") && !inQuotes) {
|
|
461
|
+
inQuotes = true;
|
|
462
|
+
quoteChar = char;
|
|
463
|
+
} else if (char === quoteChar && inQuotes) {
|
|
464
|
+
inQuotes = false;
|
|
465
|
+
quoteChar = "";
|
|
466
|
+
} else if (char === " " && !inQuotes) {
|
|
467
|
+
if (current.length > 0) {
|
|
468
|
+
args.push(current);
|
|
469
|
+
current = "";
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
current += char;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (current.length > 0) {
|
|
476
|
+
args.push(current);
|
|
477
|
+
}
|
|
478
|
+
return args;
|
|
479
|
+
}
|
|
480
|
+
function runKrolik(args, projectRoot, timeout = 3e4) {
|
|
481
|
+
const argArray = parseCommandArgs(args);
|
|
482
|
+
const result = spawnSync("node", [CLI_PATH, ...argArray], {
|
|
483
|
+
cwd: projectRoot,
|
|
484
|
+
encoding: "utf-8",
|
|
485
|
+
timeout,
|
|
486
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
487
|
+
});
|
|
488
|
+
if (result.error) {
|
|
489
|
+
return `Error: ${result.error.message}`;
|
|
490
|
+
}
|
|
491
|
+
if (result.signal === "SIGTERM") {
|
|
492
|
+
return `Error: Command timed out after ${timeout}ms`;
|
|
493
|
+
}
|
|
494
|
+
if (result.status !== 0) {
|
|
495
|
+
const errorOutput = result.stderr || result.stdout || `Exit code: ${result.status}`;
|
|
496
|
+
return errorOutput;
|
|
497
|
+
}
|
|
498
|
+
return result.stdout || "";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/mcp/tools/agent/index.ts
|
|
502
|
+
var inputSchema = {
|
|
503
|
+
...PROJECT_PROPERTY,
|
|
504
|
+
name: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: 'Agent name to run (e.g., "security-auditor", "backend-architect") or category name (e.g., "security", "quality")'
|
|
507
|
+
},
|
|
508
|
+
orchestrate: {
|
|
509
|
+
type: "boolean",
|
|
510
|
+
description: 'Enable orchestration mode. Analyzes the task and coordinates multiple specialized agents. Use when user says "use multi-agents" or "\u043C\u0443\u043B\u044C\u0442\u0438\u0430\u0433\u0435\u043D\u0442\u044B".'
|
|
511
|
+
},
|
|
512
|
+
task: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description: 'Task description for orchestration mode (e.g., "analyze security and performance", "review code quality")'
|
|
515
|
+
},
|
|
516
|
+
category: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "Filter agents by category: security, performance, architecture, quality, debugging, docs, etc."
|
|
519
|
+
},
|
|
520
|
+
file: {
|
|
521
|
+
type: "string",
|
|
522
|
+
description: "Target file for agent analysis"
|
|
523
|
+
},
|
|
524
|
+
feature: {
|
|
525
|
+
type: "string",
|
|
526
|
+
description: 'Feature or domain to focus on (e.g., "booking", "auth", "CRM")'
|
|
527
|
+
},
|
|
528
|
+
list: {
|
|
529
|
+
type: "boolean",
|
|
530
|
+
description: "List all available agents"
|
|
531
|
+
},
|
|
532
|
+
maxAgents: {
|
|
533
|
+
type: "number",
|
|
534
|
+
description: "Maximum number of agents to run in orchestration mode (default: 5)"
|
|
535
|
+
},
|
|
536
|
+
parallel: {
|
|
537
|
+
type: "boolean",
|
|
538
|
+
description: "Prefer parallel execution of agents in orchestration mode"
|
|
539
|
+
},
|
|
540
|
+
dryRun: {
|
|
541
|
+
type: "boolean",
|
|
542
|
+
description: "Show orchestration plan without executing agents"
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
function buildAgentCommand(args) {
|
|
546
|
+
const parts = ["agent"];
|
|
547
|
+
if (args.name && !args.orchestrate) {
|
|
548
|
+
const name = String(args.name).replace(/[^a-zA-Z0-9_-]/g, "");
|
|
549
|
+
parts.push(name);
|
|
550
|
+
}
|
|
551
|
+
if (args.orchestrate) {
|
|
552
|
+
parts.push("--orchestrate");
|
|
553
|
+
if (args.task) {
|
|
554
|
+
const task = String(args.task).replace(/"/g, '\\"');
|
|
555
|
+
parts.push(`--task "${task}"`);
|
|
556
|
+
}
|
|
557
|
+
if (args.maxAgents) {
|
|
558
|
+
parts.push(`--max-agents ${Number(args.maxAgents)}`);
|
|
559
|
+
}
|
|
560
|
+
if (args.parallel) {
|
|
561
|
+
parts.push("--parallel");
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (args.category) {
|
|
565
|
+
const category = String(args.category).replace(/[^a-zA-Z0-9_-]/g, "");
|
|
566
|
+
parts.push(`--category ${category}`);
|
|
567
|
+
}
|
|
568
|
+
if (args.file) {
|
|
569
|
+
const file = String(args.file).replace(/[^a-zA-Z0-9_./-]/g, "");
|
|
570
|
+
parts.push(`--file ${file}`);
|
|
571
|
+
}
|
|
572
|
+
if (args.feature) {
|
|
573
|
+
const feature = String(args.feature).replace(/[^a-zA-Z0-9_-]/g, "");
|
|
574
|
+
parts.push(`--feature ${feature}`);
|
|
575
|
+
}
|
|
576
|
+
if (args.list) {
|
|
577
|
+
parts.push("--list");
|
|
578
|
+
}
|
|
579
|
+
if (args.dryRun) {
|
|
580
|
+
parts.push("--dry-run");
|
|
581
|
+
}
|
|
582
|
+
return parts.join(" ");
|
|
583
|
+
}
|
|
584
|
+
var agentTool = {
|
|
585
|
+
name: "krolik_agent",
|
|
586
|
+
description: `Run specialized AI agents with project context. Supports orchestration mode for multi-agent coordination.
|
|
587
|
+
|
|
588
|
+
ORCHESTRATION MODE (orchestrate=true):
|
|
589
|
+
When user says "use multi-agents", "\u043C\u0443\u043B\u044C\u0442\u0438\u0430\u0433\u0435\u043D\u0442\u044B", or needs multiple expert analyses:
|
|
590
|
+
1. Set orchestrate=true and provide task description
|
|
591
|
+
2. The orchestrator analyzes the task and identifies needed agents
|
|
592
|
+
3. Creates execution plan (parallel/sequential)
|
|
593
|
+
4. Returns XML for Claude to execute with Task tool
|
|
594
|
+
|
|
595
|
+
DIRECT MODE (default):
|
|
596
|
+
Run a specific agent by name or category.
|
|
597
|
+
|
|
598
|
+
Examples:
|
|
599
|
+
- { "name": "security-auditor" } \u2192 Run security agent
|
|
600
|
+
- { "category": "quality" } \u2192 Run all quality agents
|
|
601
|
+
- { "orchestrate": true, "task": "analyze security and performance" } \u2192 Multi-agent orchestration
|
|
602
|
+
- { "list": true } \u2192 List all available agents`,
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: inputSchema
|
|
606
|
+
},
|
|
607
|
+
template: { when: "Multi-agent orchestration", params: '`orchestrate: true, task: "..."`' },
|
|
608
|
+
category: "advanced",
|
|
609
|
+
handler: (args, workspaceRoot) => {
|
|
610
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
611
|
+
const command = buildAgentCommand(args);
|
|
612
|
+
const timeout = args.orchestrate ? TIMEOUT_120S : TIMEOUT_60S;
|
|
613
|
+
return runKrolik(command, projectPath, timeout);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
registerTool(agentTool);
|
|
618
|
+
|
|
619
|
+
// src/mcp/tools/core/flag-builder.ts
|
|
620
|
+
function buildFlags(args, schema) {
|
|
621
|
+
const parts = [];
|
|
622
|
+
for (const [key, def] of Object.entries(schema)) {
|
|
623
|
+
const val = args[key];
|
|
624
|
+
if (val === void 0 || val === null || val === false) {
|
|
625
|
+
if (def.required) {
|
|
626
|
+
return { ok: false, error: `Missing required argument: ${key}` };
|
|
627
|
+
}
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (val === true) {
|
|
631
|
+
parts.push(def.flag);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
let strVal = String(val);
|
|
635
|
+
if (def.sanitize === "feature") {
|
|
636
|
+
const sanitized = sanitizeFeatureName(val);
|
|
637
|
+
if (!sanitized) {
|
|
638
|
+
return {
|
|
639
|
+
ok: false,
|
|
640
|
+
error: `Invalid ${key}: Only alphanumeric, hyphens, underscores, dots allowed.`
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
strVal = sanitized;
|
|
644
|
+
} else if (def.sanitize === "issue") {
|
|
645
|
+
const sanitized = sanitizeIssueNumber(val);
|
|
646
|
+
if (!sanitized) {
|
|
647
|
+
return { ok: false, error: `Invalid ${key}: Must be a positive number.` };
|
|
648
|
+
}
|
|
649
|
+
strVal = String(sanitized);
|
|
650
|
+
}
|
|
651
|
+
if (def.transform) {
|
|
652
|
+
const transformed = def.transform(val);
|
|
653
|
+
if (transformed === null) {
|
|
654
|
+
return { ok: false, error: `Invalid ${key}` };
|
|
655
|
+
}
|
|
656
|
+
strVal = transformed;
|
|
657
|
+
}
|
|
658
|
+
if (def.validate && !def.validate(val)) {
|
|
659
|
+
return { ok: false, error: `Invalid ${key}` };
|
|
660
|
+
}
|
|
661
|
+
if (def.flag === "") {
|
|
662
|
+
parts.push(escapeShellArg(strVal));
|
|
663
|
+
} else {
|
|
664
|
+
parts.push(`${def.flag}=${escapeShellArg(strVal)}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return { ok: true, flags: parts.join(" ") };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/mcp/tools/audit/index.ts
|
|
671
|
+
var auditSchema = {
|
|
672
|
+
path: COMMON_FLAGS.path
|
|
673
|
+
};
|
|
674
|
+
var auditTool = {
|
|
675
|
+
name: "krolik_audit",
|
|
676
|
+
description: "Audit code quality. Analyzes codebase for issues: console.log, any types, complexity, magic numbers, etc. Returns AI-friendly report.",
|
|
677
|
+
inputSchema: {
|
|
678
|
+
type: "object",
|
|
679
|
+
properties: {
|
|
680
|
+
...PROJECT_PROPERTY,
|
|
681
|
+
path: {
|
|
682
|
+
type: "string",
|
|
683
|
+
description: "Specific subdirectory within project to audit (optional)"
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
template: { when: "Code quality audit", params: "\u2014" },
|
|
688
|
+
workflow: { trigger: "on_refactor", order: 2 },
|
|
689
|
+
category: "code",
|
|
690
|
+
handler: (args, workspaceRoot) => {
|
|
691
|
+
const result = buildFlags(args, auditSchema);
|
|
692
|
+
if (!result.ok) return result.error;
|
|
693
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
694
|
+
return runKrolik(`audit ${result.flags}`, projectPath, TIMEOUT_60S);
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
registerTool(auditTool);
|
|
699
|
+
|
|
700
|
+
// src/mcp/tools/context/index.ts
|
|
701
|
+
var contextSchema = {
|
|
702
|
+
feature: COMMON_FLAGS.feature,
|
|
703
|
+
issue: COMMON_FLAGS.issue,
|
|
704
|
+
quick: { flag: "--quick" },
|
|
705
|
+
deep: { flag: "--deep" },
|
|
706
|
+
full: { flag: "--full" }
|
|
707
|
+
};
|
|
708
|
+
var contextTool = {
|
|
709
|
+
name: "krolik_context",
|
|
710
|
+
description: "Generate AI-friendly context for a specific task or feature. Returns structured XML with schema, routes, git info, and approach steps.",
|
|
711
|
+
inputSchema: {
|
|
712
|
+
type: "object",
|
|
713
|
+
properties: {
|
|
714
|
+
...PROJECT_PROPERTY,
|
|
715
|
+
feature: {
|
|
716
|
+
type: "string",
|
|
717
|
+
description: 'The feature or task to analyze (e.g., "booking", "auth", "CRM")'
|
|
718
|
+
},
|
|
719
|
+
issue: {
|
|
720
|
+
type: "string",
|
|
721
|
+
description: "GitHub issue number to get context for"
|
|
722
|
+
},
|
|
723
|
+
quick: {
|
|
724
|
+
type: "boolean",
|
|
725
|
+
description: "Quick mode: architecture, git, tree, schema, routes only (faster)"
|
|
726
|
+
},
|
|
727
|
+
deep: {
|
|
728
|
+
type: "boolean",
|
|
729
|
+
description: "Deep mode: imports, types, env, contracts (complements quick)"
|
|
730
|
+
},
|
|
731
|
+
full: {
|
|
732
|
+
type: "boolean",
|
|
733
|
+
description: "Full mode: all enrichment (--include-code --domain-history --show-deps --with-audit)"
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
template: { when: "Before feature/issue work", params: '`feature: "..."` or `issue: "123"`' },
|
|
738
|
+
workflow: { trigger: "before_task", order: 1 },
|
|
739
|
+
category: "context",
|
|
740
|
+
handler: (args, workspaceRoot) => {
|
|
741
|
+
const result = buildFlags(args, contextSchema);
|
|
742
|
+
if (!result.ok) return result.error;
|
|
743
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
744
|
+
return runKrolik(`context ${result.flags}`, projectPath, TIMEOUT_60S);
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
registerTool(contextTool);
|
|
749
|
+
|
|
750
|
+
// src/mcp/tools/docs/index.ts
|
|
751
|
+
var docsSchema = {
|
|
752
|
+
library: { flag: "--library", sanitize: "none" },
|
|
753
|
+
topic: { flag: "--topic", sanitize: "none" },
|
|
754
|
+
limit: { flag: "--limit", sanitize: "none" },
|
|
755
|
+
force: { flag: "--force" },
|
|
756
|
+
expired: { flag: "--expired" }
|
|
757
|
+
};
|
|
758
|
+
var docsTool = {
|
|
759
|
+
name: "krolik_docs",
|
|
760
|
+
description: `Query and manage cached library documentation.
|
|
761
|
+
|
|
762
|
+
Actions:
|
|
763
|
+
- search: Full-text search across cached docs (FTS5)
|
|
764
|
+
- list: List all cached libraries with expiry status
|
|
765
|
+
- fetch: Fetch/refresh docs for a library from Context7
|
|
766
|
+
- detect: Auto-detect libraries from package.json
|
|
767
|
+
- clear: Clear cache (all or specific library)
|
|
768
|
+
|
|
769
|
+
Use this tool to:
|
|
770
|
+
- Find code examples and API documentation
|
|
771
|
+
- Check which libraries are cached
|
|
772
|
+
- Refresh outdated documentation
|
|
773
|
+
- Discover project dependencies that have docs available
|
|
774
|
+
|
|
775
|
+
Examples:
|
|
776
|
+
- Search: { action: "search", query: "app router server components" }
|
|
777
|
+
- List: { action: "list" }
|
|
778
|
+
- Fetch: { action: "fetch", library: "next.js", topic: "app-router" }
|
|
779
|
+
- Detect: { action: "detect" }`,
|
|
780
|
+
inputSchema: {
|
|
781
|
+
type: "object",
|
|
782
|
+
properties: {
|
|
783
|
+
...PROJECT_PROPERTY,
|
|
784
|
+
action: {
|
|
785
|
+
type: "string",
|
|
786
|
+
enum: ["search", "list", "fetch", "detect", "clear"],
|
|
787
|
+
description: "Action to perform"
|
|
788
|
+
},
|
|
789
|
+
query: {
|
|
790
|
+
type: "string",
|
|
791
|
+
description: "Search query (for action: search)"
|
|
792
|
+
},
|
|
793
|
+
library: {
|
|
794
|
+
type: "string",
|
|
795
|
+
description: 'Library name (e.g., "next.js", "prisma", "trpc")'
|
|
796
|
+
},
|
|
797
|
+
topic: {
|
|
798
|
+
type: "string",
|
|
799
|
+
description: 'Topic to focus on (e.g., "app-router", "transactions")'
|
|
800
|
+
},
|
|
801
|
+
limit: {
|
|
802
|
+
type: "number",
|
|
803
|
+
description: "Max results for search (default: 10)"
|
|
804
|
+
},
|
|
805
|
+
force: {
|
|
806
|
+
type: "boolean",
|
|
807
|
+
description: "Force refresh even if cache is valid"
|
|
808
|
+
},
|
|
809
|
+
expired: {
|
|
810
|
+
type: "boolean",
|
|
811
|
+
description: "For list/clear: only show/clear expired entries"
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
required: ["action"]
|
|
815
|
+
},
|
|
816
|
+
template: { when: "Need library API docs", params: '`action: "search", query: "..."`' },
|
|
817
|
+
category: "context",
|
|
818
|
+
handler: (args, workspaceRoot) => {
|
|
819
|
+
const action = args.action;
|
|
820
|
+
if (action === "search" && !args.query) {
|
|
821
|
+
return "Error: query is required for search action";
|
|
822
|
+
}
|
|
823
|
+
if (action === "fetch" && !args.library) {
|
|
824
|
+
return "Error: library is required for fetch action";
|
|
825
|
+
}
|
|
826
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
827
|
+
switch (action) {
|
|
828
|
+
case "search": {
|
|
829
|
+
const query = args.query;
|
|
830
|
+
const result = buildFlags(args, {
|
|
831
|
+
library: docsSchema.library,
|
|
832
|
+
topic: docsSchema.topic,
|
|
833
|
+
limit: docsSchema.limit
|
|
834
|
+
});
|
|
835
|
+
if (!result.ok) return result.error;
|
|
836
|
+
return runKrolik(`docs search "${query}" ${result.flags}`, projectPath, TIMEOUT_120S);
|
|
837
|
+
}
|
|
838
|
+
case "list": {
|
|
839
|
+
const result = buildFlags(args, {
|
|
840
|
+
expired: docsSchema.expired
|
|
841
|
+
});
|
|
842
|
+
if (!result.ok) return result.error;
|
|
843
|
+
return runKrolik(`docs list ${result.flags}`, projectPath, TIMEOUT_120S);
|
|
844
|
+
}
|
|
845
|
+
case "fetch": {
|
|
846
|
+
const library = args.library;
|
|
847
|
+
const result = buildFlags(args, {
|
|
848
|
+
topic: docsSchema.topic,
|
|
849
|
+
force: docsSchema.force
|
|
850
|
+
});
|
|
851
|
+
if (!result.ok) return result.error;
|
|
852
|
+
return runKrolik(`docs fetch "${library}" ${result.flags}`, projectPath, TIMEOUT_120S);
|
|
853
|
+
}
|
|
854
|
+
case "detect": {
|
|
855
|
+
return runKrolik("docs detect", projectPath, TIMEOUT_120S);
|
|
856
|
+
}
|
|
857
|
+
case "clear": {
|
|
858
|
+
const result = buildFlags(args, {
|
|
859
|
+
library: docsSchema.library,
|
|
860
|
+
expired: docsSchema.expired
|
|
861
|
+
});
|
|
862
|
+
if (!result.ok) return result.error;
|
|
863
|
+
return runKrolik(`docs clear ${result.flags}`, projectPath, TIMEOUT_120S);
|
|
864
|
+
}
|
|
865
|
+
default:
|
|
866
|
+
return `Error: Unknown action: ${action}. Valid actions: search, list, fetch, detect, clear`;
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
registerTool(docsTool);
|
|
872
|
+
|
|
873
|
+
// src/mcp/tools/fix/index.ts
|
|
874
|
+
var fixSchema = {
|
|
875
|
+
dryRun: COMMON_FLAGS.dryRun,
|
|
876
|
+
safe: COMMON_FLAGS.safe,
|
|
877
|
+
path: COMMON_FLAGS.path,
|
|
878
|
+
category: {
|
|
879
|
+
flag: "--category",
|
|
880
|
+
validate: (val) => typeof val === "string" && FIX_CATEGORIES.includes(val)
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
var fixTool = {
|
|
884
|
+
name: "krolik_fix",
|
|
885
|
+
description: "Auto-fix code quality issues. Removes console.log, debugger, replaces any with unknown, etc. Use --dry-run to preview.",
|
|
886
|
+
inputSchema: {
|
|
887
|
+
type: "object",
|
|
888
|
+
properties: {
|
|
889
|
+
...PROJECT_PROPERTY,
|
|
890
|
+
dryRun: {
|
|
891
|
+
type: "boolean",
|
|
892
|
+
description: "Preview changes without applying (recommended first)"
|
|
893
|
+
},
|
|
894
|
+
path: {
|
|
895
|
+
type: "string",
|
|
896
|
+
description: "Specific directory to fix (optional)"
|
|
897
|
+
},
|
|
898
|
+
category: {
|
|
899
|
+
type: "string",
|
|
900
|
+
description: "Fix category: lint, type-safety, complexity, hardcoded, srp",
|
|
901
|
+
enum: [...FIX_CATEGORIES]
|
|
902
|
+
},
|
|
903
|
+
safe: {
|
|
904
|
+
type: "boolean",
|
|
905
|
+
description: "Only apply safe fixes (no risky changes)"
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
template: { when: "Quality issues found", params: "`dryRun: true` first" },
|
|
910
|
+
workflow: { trigger: "after_code", order: 1 },
|
|
911
|
+
category: "code",
|
|
912
|
+
handler: (args, workspaceRoot) => {
|
|
913
|
+
const result = buildFlags(args, fixSchema);
|
|
914
|
+
if (!result.ok) {
|
|
915
|
+
if (args.category && !FIX_CATEGORIES.includes(args.category)) {
|
|
916
|
+
return `Error: Invalid category. Must be one of: ${FIX_CATEGORIES.join(", ")}`;
|
|
917
|
+
}
|
|
918
|
+
return result.error;
|
|
919
|
+
}
|
|
920
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
921
|
+
return runKrolik(`fix ${result.flags}`, projectPath, TIMEOUT_60S);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
registerTool(fixTool);
|
|
926
|
+
|
|
927
|
+
// src/mcp/tools/issue/index.ts
|
|
928
|
+
var issueSchema = {
|
|
929
|
+
number: { flag: "", sanitize: "issue", required: true }
|
|
930
|
+
};
|
|
931
|
+
var issueTool = {
|
|
932
|
+
name: "krolik_issue",
|
|
933
|
+
description: "Parse a GitHub issue and extract context: checklist, mentioned files, priority.",
|
|
934
|
+
inputSchema: {
|
|
935
|
+
type: "object",
|
|
936
|
+
properties: {
|
|
937
|
+
number: {
|
|
938
|
+
type: "string",
|
|
939
|
+
description: "GitHub issue number"
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
required: ["number"]
|
|
943
|
+
},
|
|
944
|
+
template: { when: "Parse GitHub issue details", params: '`number: "123"`' },
|
|
945
|
+
workflow: { trigger: "before_task", order: 2 },
|
|
946
|
+
category: "context",
|
|
947
|
+
handler: (args, projectRoot) => {
|
|
948
|
+
const result = buildFlags(args, issueSchema);
|
|
949
|
+
if (!result.ok) return result.error;
|
|
950
|
+
return runKrolik(`issue ${result.flags}`, projectRoot);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
registerTool(issueTool);
|
|
954
|
+
|
|
955
|
+
// src/mcp/tools/memory/index.ts
|
|
956
|
+
var memSaveTool = {
|
|
957
|
+
name: "krolik_mem_save",
|
|
958
|
+
description: `Save memory: observation, decision, pattern, bugfix, or feature.
|
|
959
|
+
|
|
960
|
+
Examples:
|
|
961
|
+
- "Decided to use tRPC for type safety" (decision)
|
|
962
|
+
- "Fixed race condition with mutex" (bugfix)
|
|
963
|
+
- "All routes use validate -> execute -> audit pattern" (pattern)
|
|
964
|
+
- "Users prefer dark mode toggle in header" (observation)
|
|
965
|
+
- "Implemented real-time notifications with WebSockets" (feature)`,
|
|
966
|
+
template: { when: "Save decision/pattern/bugfix", params: '`type: "decision", title: "..."`' },
|
|
967
|
+
workflow: { trigger: "on_decision", order: 1 },
|
|
968
|
+
category: "memory",
|
|
969
|
+
inputSchema: {
|
|
970
|
+
type: "object",
|
|
971
|
+
properties: {
|
|
972
|
+
...PROJECT_PROPERTY,
|
|
973
|
+
type: {
|
|
974
|
+
type: "string",
|
|
975
|
+
enum: ["observation", "decision", "pattern", "bugfix", "feature"],
|
|
976
|
+
description: "Type of memory entry"
|
|
977
|
+
},
|
|
978
|
+
title: {
|
|
979
|
+
type: "string",
|
|
980
|
+
description: 'Short summary (e.g., "Use tRPC for type safety")'
|
|
981
|
+
},
|
|
982
|
+
description: {
|
|
983
|
+
type: "string",
|
|
984
|
+
description: "Detailed description of the memory entry"
|
|
985
|
+
},
|
|
986
|
+
importance: {
|
|
987
|
+
type: "string",
|
|
988
|
+
enum: ["low", "medium", "high", "critical"],
|
|
989
|
+
description: "Importance level (default: medium)"
|
|
990
|
+
},
|
|
991
|
+
tags: {
|
|
992
|
+
type: "string",
|
|
993
|
+
description: 'Comma-separated tags (e.g., "api, typescript, performance")'
|
|
994
|
+
},
|
|
995
|
+
files: {
|
|
996
|
+
type: "string",
|
|
997
|
+
description: "Comma-separated file paths related to this memory"
|
|
998
|
+
},
|
|
999
|
+
features: {
|
|
1000
|
+
type: "string",
|
|
1001
|
+
description: 'Comma-separated features/domains (e.g., "booking, auth")'
|
|
1002
|
+
}
|
|
1003
|
+
},
|
|
1004
|
+
required: ["type", "title", "description"]
|
|
1005
|
+
},
|
|
1006
|
+
handler: (args, workspaceRoot) => {
|
|
1007
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1008
|
+
const flags = [];
|
|
1009
|
+
if (args.type) flags.push(`--type "${args.type}"`);
|
|
1010
|
+
if (args.title) flags.push(`--title "${String(args.title).replace(/"/g, '\\"')}"`);
|
|
1011
|
+
if (args.description)
|
|
1012
|
+
flags.push(`--description "${String(args.description).replace(/"/g, '\\"')}"`);
|
|
1013
|
+
if (args.importance) flags.push(`--importance "${args.importance}"`);
|
|
1014
|
+
if (args.tags) flags.push(`--tags "${args.tags}"`);
|
|
1015
|
+
if (args.files) flags.push(`--files "${args.files}"`);
|
|
1016
|
+
if (args.features) flags.push(`--features "${args.features}"`);
|
|
1017
|
+
return runKrolik(`mem save ${flags.join(" ")}`, projectPath, TIMEOUT_60S);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
var memSearchTool = {
|
|
1022
|
+
name: "krolik_mem_search",
|
|
1023
|
+
description: `Search memory entries by query, type, tags, or feature.
|
|
1024
|
+
|
|
1025
|
+
Examples:
|
|
1026
|
+
- Search for "tRPC" decisions
|
|
1027
|
+
- Find all "performance" related entries
|
|
1028
|
+
- Search patterns for "authentication"`,
|
|
1029
|
+
template: { when: "Search memories by query", params: '`query: "authentication"`' },
|
|
1030
|
+
category: "memory",
|
|
1031
|
+
inputSchema: {
|
|
1032
|
+
type: "object",
|
|
1033
|
+
properties: {
|
|
1034
|
+
...PROJECT_PROPERTY,
|
|
1035
|
+
query: {
|
|
1036
|
+
type: "string",
|
|
1037
|
+
description: "Search query (searches in title and description)"
|
|
1038
|
+
},
|
|
1039
|
+
type: {
|
|
1040
|
+
type: "string",
|
|
1041
|
+
enum: ["observation", "decision", "pattern", "bugfix", "feature"],
|
|
1042
|
+
description: "Filter by memory type"
|
|
1043
|
+
},
|
|
1044
|
+
tags: {
|
|
1045
|
+
type: "string",
|
|
1046
|
+
description: "Filter by tags (comma-separated)"
|
|
1047
|
+
},
|
|
1048
|
+
features: {
|
|
1049
|
+
type: "string",
|
|
1050
|
+
description: "Filter by feature/domain (comma-separated)"
|
|
1051
|
+
},
|
|
1052
|
+
limit: {
|
|
1053
|
+
type: "number",
|
|
1054
|
+
description: "Maximum number of results (default: 10)"
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
handler: (args, workspaceRoot) => {
|
|
1059
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1060
|
+
const flags = [];
|
|
1061
|
+
if (args.query) flags.push(`--query "${String(args.query).replace(/"/g, '\\"')}"`);
|
|
1062
|
+
if (args.type) flags.push(`--type "${args.type}"`);
|
|
1063
|
+
if (args.tags) flags.push(`--tags "${args.tags}"`);
|
|
1064
|
+
if (args.features) flags.push(`--features "${args.features}"`);
|
|
1065
|
+
if (args.limit) flags.push(`--limit ${args.limit}`);
|
|
1066
|
+
return runKrolik(`mem search ${flags.join(" ")}`, projectPath, TIMEOUT_60S);
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
var memRecentTool = {
|
|
1071
|
+
name: "krolik_mem_recent",
|
|
1072
|
+
description: `Get recent memory entries, optionally filtered by type.
|
|
1073
|
+
|
|
1074
|
+
Useful for:
|
|
1075
|
+
- Reviewing recent decisions
|
|
1076
|
+
- Checking latest bugfixes
|
|
1077
|
+
- Seeing recent patterns added`,
|
|
1078
|
+
template: { when: "Get recent memories", params: "`limit: 5`" },
|
|
1079
|
+
workflow: { trigger: "session_start", order: 2 },
|
|
1080
|
+
category: "memory",
|
|
1081
|
+
inputSchema: {
|
|
1082
|
+
type: "object",
|
|
1083
|
+
properties: {
|
|
1084
|
+
...PROJECT_PROPERTY,
|
|
1085
|
+
limit: {
|
|
1086
|
+
type: "number",
|
|
1087
|
+
description: "Maximum number of entries to return (default: 10)"
|
|
1088
|
+
},
|
|
1089
|
+
type: {
|
|
1090
|
+
type: "string",
|
|
1091
|
+
enum: ["observation", "decision", "pattern", "bugfix", "feature"],
|
|
1092
|
+
description: "Filter by memory type"
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
handler: (args, workspaceRoot) => {
|
|
1097
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1098
|
+
const flags = [];
|
|
1099
|
+
if (args.limit) flags.push(`--limit ${args.limit}`);
|
|
1100
|
+
if (args.type) flags.push(`--type "${args.type}"`);
|
|
1101
|
+
return runKrolik(`mem recent ${flags.join(" ")}`, projectPath, TIMEOUT_60S);
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
registerTool(memSaveTool);
|
|
1106
|
+
registerTool(memSearchTool);
|
|
1107
|
+
registerTool(memRecentTool);
|
|
1108
|
+
|
|
1109
|
+
// src/mcp/tools/refactor/index.ts
|
|
1110
|
+
var refactorSchema = {
|
|
1111
|
+
path: COMMON_FLAGS.path,
|
|
1112
|
+
package: { flag: "--package", sanitize: "feature" },
|
|
1113
|
+
allPackages: { flag: "--all-packages" },
|
|
1114
|
+
duplicatesOnly: { flag: "--duplicates-only" },
|
|
1115
|
+
typesOnly: { flag: "--types-only" },
|
|
1116
|
+
includeTypes: { flag: "--include-types" },
|
|
1117
|
+
structureOnly: { flag: "--structure-only" },
|
|
1118
|
+
dryRun: COMMON_FLAGS.dryRun,
|
|
1119
|
+
apply: COMMON_FLAGS.apply,
|
|
1120
|
+
fixTypes: { flag: "--fix-types" }
|
|
1121
|
+
};
|
|
1122
|
+
var refactorTool = {
|
|
1123
|
+
name: "krolik_refactor",
|
|
1124
|
+
description: "Analyze and refactor module structure. Finds duplicate functions/types, analyzes structure, suggests migrations.",
|
|
1125
|
+
inputSchema: {
|
|
1126
|
+
type: "object",
|
|
1127
|
+
properties: {
|
|
1128
|
+
...PROJECT_PROPERTY,
|
|
1129
|
+
path: {
|
|
1130
|
+
type: "string",
|
|
1131
|
+
description: "Path to analyze (default: auto-detect for monorepo)"
|
|
1132
|
+
},
|
|
1133
|
+
package: {
|
|
1134
|
+
type: "string",
|
|
1135
|
+
description: "Monorepo package to analyze (e.g., web, api)"
|
|
1136
|
+
},
|
|
1137
|
+
allPackages: {
|
|
1138
|
+
type: "boolean",
|
|
1139
|
+
description: "Analyze all packages in monorepo"
|
|
1140
|
+
},
|
|
1141
|
+
duplicatesOnly: {
|
|
1142
|
+
type: "boolean",
|
|
1143
|
+
description: "Only analyze duplicate functions"
|
|
1144
|
+
},
|
|
1145
|
+
typesOnly: {
|
|
1146
|
+
type: "boolean",
|
|
1147
|
+
description: "Only analyze duplicate types/interfaces"
|
|
1148
|
+
},
|
|
1149
|
+
includeTypes: {
|
|
1150
|
+
type: "boolean",
|
|
1151
|
+
description: "Include type/interface duplicate detection"
|
|
1152
|
+
},
|
|
1153
|
+
structureOnly: {
|
|
1154
|
+
type: "boolean",
|
|
1155
|
+
description: "Only analyze module structure"
|
|
1156
|
+
},
|
|
1157
|
+
dryRun: {
|
|
1158
|
+
type: "boolean",
|
|
1159
|
+
description: "Show migration plan without applying"
|
|
1160
|
+
},
|
|
1161
|
+
apply: {
|
|
1162
|
+
type: "boolean",
|
|
1163
|
+
description: "Apply migrations (move files, update imports)"
|
|
1164
|
+
},
|
|
1165
|
+
fixTypes: {
|
|
1166
|
+
type: "boolean",
|
|
1167
|
+
description: "Auto-fix type duplicates (merge identical types)"
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
},
|
|
1171
|
+
template: { when: "Find duplicates/structure", params: "`dryRun: true`" },
|
|
1172
|
+
workflow: { trigger: "on_refactor", order: 1 },
|
|
1173
|
+
category: "code",
|
|
1174
|
+
handler: (args, workspaceRoot) => {
|
|
1175
|
+
const result = buildFlags(args, refactorSchema);
|
|
1176
|
+
if (!result.ok) {
|
|
1177
|
+
if (args.package && result.error.includes("package")) {
|
|
1178
|
+
return "Error: Invalid package name.";
|
|
1179
|
+
}
|
|
1180
|
+
return result.error;
|
|
1181
|
+
}
|
|
1182
|
+
const flags = result.flags ? `${result.flags} --ai` : "--ai";
|
|
1183
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1184
|
+
return runKrolik(`refactor ${flags}`, projectPath, TIMEOUT_60S);
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
registerTool(refactorTool);
|
|
1189
|
+
|
|
1190
|
+
// src/mcp/tools/review/index.ts
|
|
1191
|
+
var reviewSchema = {
|
|
1192
|
+
staged: COMMON_FLAGS.staged,
|
|
1193
|
+
pr: COMMON_FLAGS.pr
|
|
1194
|
+
};
|
|
1195
|
+
var reviewTool = {
|
|
1196
|
+
name: "krolik_review",
|
|
1197
|
+
description: "Review code changes. Analyzes git diff for security issues, performance problems, and risks.",
|
|
1198
|
+
inputSchema: {
|
|
1199
|
+
type: "object",
|
|
1200
|
+
properties: {
|
|
1201
|
+
...PROJECT_PROPERTY,
|
|
1202
|
+
staged: {
|
|
1203
|
+
type: "boolean",
|
|
1204
|
+
description: "Review only staged changes"
|
|
1205
|
+
},
|
|
1206
|
+
pr: {
|
|
1207
|
+
type: "string",
|
|
1208
|
+
description: "Review specific PR number"
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
template: { when: "After code changes", params: "`staged: true`" },
|
|
1213
|
+
workflow: { trigger: "before_commit", order: 1 },
|
|
1214
|
+
category: "code",
|
|
1215
|
+
handler: (args, workspaceRoot) => {
|
|
1216
|
+
const result = buildFlags(args, reviewSchema);
|
|
1217
|
+
if (!result.ok) return result.error;
|
|
1218
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1219
|
+
return runKrolik(`review ${result.flags}`, projectPath, TIMEOUT_60S);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
registerTool(reviewTool);
|
|
1224
|
+
|
|
1225
|
+
// src/mcp/tools/routes/index.ts
|
|
1226
|
+
var routesFlagSchema = {
|
|
1227
|
+
json: COMMON_FLAGS.json
|
|
1228
|
+
};
|
|
1229
|
+
var routesTool = {
|
|
1230
|
+
name: "krolik_routes",
|
|
1231
|
+
description: "Analyze tRPC API routes. Returns all procedures with types, inputs, and protection status.",
|
|
1232
|
+
inputSchema: {
|
|
1233
|
+
type: "object",
|
|
1234
|
+
properties: {
|
|
1235
|
+
...PROJECT_PROPERTY,
|
|
1236
|
+
json: {
|
|
1237
|
+
type: "boolean",
|
|
1238
|
+
description: "Return JSON format instead of markdown"
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
template: { when: "API routes questions", params: "\u2014" },
|
|
1243
|
+
category: "context",
|
|
1244
|
+
handler: (args, workspaceRoot) => {
|
|
1245
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1246
|
+
const result = buildFlags(args, routesFlagSchema);
|
|
1247
|
+
if (!result.ok) return result.error;
|
|
1248
|
+
return runKrolik(`routes ${result.flags}`, projectPath);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
registerTool(routesTool);
|
|
1253
|
+
|
|
1254
|
+
// src/mcp/tools/schema/index.ts
|
|
1255
|
+
var schemaFlagSchema = {
|
|
1256
|
+
json: COMMON_FLAGS.json
|
|
1257
|
+
};
|
|
1258
|
+
var schemaTool = {
|
|
1259
|
+
name: "krolik_schema",
|
|
1260
|
+
description: "Analyze Prisma database schema. Returns all models, fields, relations, and enums.",
|
|
1261
|
+
inputSchema: {
|
|
1262
|
+
type: "object",
|
|
1263
|
+
properties: {
|
|
1264
|
+
...PROJECT_PROPERTY,
|
|
1265
|
+
json: {
|
|
1266
|
+
type: "boolean",
|
|
1267
|
+
description: "Return JSON format instead of markdown"
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
template: { when: "DB schema questions", params: "\u2014" },
|
|
1272
|
+
category: "context",
|
|
1273
|
+
handler: (args, workspaceRoot) => {
|
|
1274
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1275
|
+
const result = buildFlags(args, schemaFlagSchema);
|
|
1276
|
+
if (!result.ok) return result.error;
|
|
1277
|
+
return runKrolik(`schema ${result.flags}`, projectPath);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
registerTool(schemaTool);
|
|
1282
|
+
|
|
1283
|
+
// src/mcp/tools/status/index.ts
|
|
1284
|
+
var statusTool = {
|
|
1285
|
+
name: "krolik_status",
|
|
1286
|
+
description: "Get project diagnostics: git status, typecheck, lint, TODOs. Use this to understand the current state of the project.",
|
|
1287
|
+
inputSchema: {
|
|
1288
|
+
type: "object",
|
|
1289
|
+
properties: {
|
|
1290
|
+
...PROJECT_PROPERTY,
|
|
1291
|
+
fast: {
|
|
1292
|
+
type: "boolean",
|
|
1293
|
+
description: "Skip slow checks (typecheck, lint) for faster response"
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
template: { when: "Session start", params: "`fast: true`" },
|
|
1298
|
+
workflow: { trigger: "session_start", order: 1 },
|
|
1299
|
+
category: "start",
|
|
1300
|
+
handler: (args, workspaceRoot) => {
|
|
1301
|
+
return withProjectDetection(args, workspaceRoot, (projectPath) => {
|
|
1302
|
+
const flags = args.fast ? "--fast" : "";
|
|
1303
|
+
return runKrolik(`status ${flags}`, projectPath);
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
registerTool(statusTool);
|
|
1308
|
+
var DEFAULT_SKIP_DIRS = ["node_modules", ".next", "dist", ".git", ".turbo", "coverage", ".cache"];
|
|
1309
|
+
function exists(filePath) {
|
|
1310
|
+
return fs6.existsSync(filePath);
|
|
1311
|
+
}
|
|
1312
|
+
function isDirectory(filePath) {
|
|
1313
|
+
try {
|
|
1314
|
+
return fs6.statSync(filePath).isDirectory();
|
|
1315
|
+
} catch {
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function readFile(filePath) {
|
|
1320
|
+
try {
|
|
1321
|
+
return fs6.readFileSync(filePath, "utf-8");
|
|
1322
|
+
} catch {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
function writeFile(filePath, content) {
|
|
1327
|
+
try {
|
|
1328
|
+
const dir = path12.dirname(filePath);
|
|
1329
|
+
if (!exists(dir)) {
|
|
1330
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1331
|
+
}
|
|
1332
|
+
fs6.writeFileSync(filePath, content, "utf-8");
|
|
1333
|
+
return true;
|
|
1334
|
+
} catch {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
function readJson(filePath) {
|
|
1339
|
+
const content = readFile(filePath);
|
|
1340
|
+
if (!content) return null;
|
|
1341
|
+
try {
|
|
1342
|
+
return JSON.parse(content);
|
|
1343
|
+
} catch {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
function findFiles(dir, options = {}) {
|
|
1348
|
+
const { extensions = [], skipDirs = DEFAULT_SKIP_DIRS, maxDepth = Infinity } = options;
|
|
1349
|
+
const files = [];
|
|
1350
|
+
function walk(currentDir, depth) {
|
|
1351
|
+
if (depth > maxDepth || !exists(currentDir)) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
let entries;
|
|
1355
|
+
try {
|
|
1356
|
+
entries = fs6.readdirSync(currentDir, { withFileTypes: true });
|
|
1357
|
+
} catch {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
for (const entry of entries) {
|
|
1361
|
+
const fullPath = path12.join(currentDir, entry.name);
|
|
1362
|
+
if (entry.isDirectory()) {
|
|
1363
|
+
if (!skipDirs.includes(entry.name)) {
|
|
1364
|
+
walk(fullPath, depth + 1);
|
|
1365
|
+
}
|
|
1366
|
+
} else if (entry.isFile()) {
|
|
1367
|
+
const ext = path12.extname(entry.name);
|
|
1368
|
+
if (extensions.length === 0 || extensions.includes(ext)) {
|
|
1369
|
+
files.push(fullPath);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
walk(dir, 0);
|
|
1375
|
+
return files;
|
|
1376
|
+
}
|
|
1377
|
+
function exec(command, options = {}) {
|
|
1378
|
+
const { cwd, silent = true, timeout = 3e4, env } = options;
|
|
1379
|
+
const execOptions = {
|
|
1380
|
+
cwd,
|
|
1381
|
+
encoding: "utf-8",
|
|
1382
|
+
stdio: silent ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
1383
|
+
timeout,
|
|
1384
|
+
env: env ? { ...process.env, ...env } : void 0
|
|
1385
|
+
};
|
|
1386
|
+
return execSync(command, execOptions).toString().trim();
|
|
1387
|
+
}
|
|
1388
|
+
function tryExec(command, options = {}) {
|
|
1389
|
+
try {
|
|
1390
|
+
const output = exec(command, options);
|
|
1391
|
+
return { success: true, output };
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
const err = error;
|
|
1394
|
+
return {
|
|
1395
|
+
success: false,
|
|
1396
|
+
output: err.stdout?.toString() ?? "",
|
|
1397
|
+
error: err.stderr?.toString() ?? err.message ?? "Unknown error"
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function execLines(command, options = {}) {
|
|
1402
|
+
const result = tryExec(command, options);
|
|
1403
|
+
if (!result.success || !result.output) {
|
|
1404
|
+
return [];
|
|
1405
|
+
}
|
|
1406
|
+
return result.output.split("\n").filter(Boolean);
|
|
1407
|
+
}
|
|
1408
|
+
function shellOpts(cwd) {
|
|
1409
|
+
const opts = { silent: true };
|
|
1410
|
+
if (cwd) opts.cwd = cwd;
|
|
1411
|
+
return opts;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/lib/@git/local.ts
|
|
1415
|
+
var MAGIC_3_VALUE = 3;
|
|
1416
|
+
var MAGIC_3 = MAGIC_3_VALUE;
|
|
1417
|
+
function isGitRepo(cwd) {
|
|
1418
|
+
const result = tryExec("git rev-parse --is-inside-work-tree", shellOpts(cwd));
|
|
1419
|
+
return result.success && result.output === "true";
|
|
1420
|
+
}
|
|
1421
|
+
function getCurrentBranch(cwd) {
|
|
1422
|
+
const result = tryExec("git branch --show-current", shellOpts(cwd));
|
|
1423
|
+
return result.success ? result.output : null;
|
|
1424
|
+
}
|
|
1425
|
+
function getStatus(cwd) {
|
|
1426
|
+
const result = tryExec("git status --porcelain", shellOpts(cwd));
|
|
1427
|
+
const lines = result.success ? result.output.split("\n").filter(Boolean) : [];
|
|
1428
|
+
const modified = [];
|
|
1429
|
+
const untracked = [];
|
|
1430
|
+
const staged = [];
|
|
1431
|
+
for (const line of lines) {
|
|
1432
|
+
const status = line.slice(0, 2);
|
|
1433
|
+
const file = line.slice(MAGIC_3);
|
|
1434
|
+
if (status.startsWith("?")) {
|
|
1435
|
+
untracked.push(file);
|
|
1436
|
+
} else if (status[0] !== " ") {
|
|
1437
|
+
staged.push(file);
|
|
1438
|
+
}
|
|
1439
|
+
if (status[1] === "M" || status[0] === "M") {
|
|
1440
|
+
modified.push(file);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
modified,
|
|
1445
|
+
untracked,
|
|
1446
|
+
staged,
|
|
1447
|
+
hasChanges: lines.length > 0
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
var LOG_LEVELS = {
|
|
1451
|
+
debug: 0,
|
|
1452
|
+
info: 1,
|
|
1453
|
+
warn: 2,
|
|
1454
|
+
error: 3,
|
|
1455
|
+
silent: 4
|
|
1456
|
+
};
|
|
1457
|
+
function createLogger(options = {}) {
|
|
1458
|
+
const { level = "info", colors = true, write = console.log } = options;
|
|
1459
|
+
const shouldLog = (msgLevel) => {
|
|
1460
|
+
return LOG_LEVELS[msgLevel] >= LOG_LEVELS[level];
|
|
1461
|
+
};
|
|
1462
|
+
const format = colors ? {
|
|
1463
|
+
debug: (msg) => chalk.dim(msg),
|
|
1464
|
+
info: (msg) => chalk.cyan(msg),
|
|
1465
|
+
warn: (msg) => chalk.yellow(msg),
|
|
1466
|
+
error: (msg) => chalk.red(msg),
|
|
1467
|
+
success: (msg) => chalk.green(msg),
|
|
1468
|
+
header: (msg) => chalk.bold.blue(msg)
|
|
1469
|
+
} : {
|
|
1470
|
+
debug: (msg) => msg,
|
|
1471
|
+
info: (msg) => msg,
|
|
1472
|
+
warn: (msg) => `[WARN] ${msg}`,
|
|
1473
|
+
error: (msg) => `[ERROR] ${msg}`,
|
|
1474
|
+
success: (msg) => msg,
|
|
1475
|
+
header: (msg) => msg
|
|
1476
|
+
};
|
|
1477
|
+
return {
|
|
1478
|
+
debug(message) {
|
|
1479
|
+
if (shouldLog("debug")) {
|
|
1480
|
+
write(format.debug(message));
|
|
1481
|
+
}
|
|
1482
|
+
},
|
|
1483
|
+
info(message) {
|
|
1484
|
+
if (shouldLog("info")) {
|
|
1485
|
+
write(format.info(message));
|
|
1486
|
+
}
|
|
1487
|
+
},
|
|
1488
|
+
warn(message) {
|
|
1489
|
+
if (shouldLog("warn")) {
|
|
1490
|
+
write(format.warn(message));
|
|
1491
|
+
}
|
|
1492
|
+
},
|
|
1493
|
+
error(message) {
|
|
1494
|
+
if (shouldLog("error")) {
|
|
1495
|
+
write(format.error(message));
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
success(message) {
|
|
1499
|
+
if (shouldLog("info")) {
|
|
1500
|
+
write(format.success(message));
|
|
1501
|
+
}
|
|
1502
|
+
},
|
|
1503
|
+
section(title) {
|
|
1504
|
+
if (shouldLog("info")) {
|
|
1505
|
+
write("");
|
|
1506
|
+
write(format.header("\u2550".repeat(60)));
|
|
1507
|
+
write(format.header(` ${title}`));
|
|
1508
|
+
write(format.header("\u2550".repeat(60)));
|
|
1509
|
+
write("");
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
box(lines, type = "info") {
|
|
1513
|
+
if (!shouldLog("info")) return;
|
|
1514
|
+
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
1515
|
+
const border = "\u2500".repeat(maxLen + 2);
|
|
1516
|
+
const colorFn = type === "success" ? format.success : type === "warning" ? format.warn : type === "error" ? format.error : format.info;
|
|
1517
|
+
write(colorFn(`\u250C${border}\u2510`));
|
|
1518
|
+
for (const line of lines) {
|
|
1519
|
+
write(colorFn(`\u2502 ${line.padEnd(maxLen)} \u2502`));
|
|
1520
|
+
}
|
|
1521
|
+
write(colorFn(`\u2514${border}\u2518`));
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
createLogger();
|
|
1526
|
+
/* @__PURE__ */ new Set([
|
|
1527
|
+
SyntaxKind.IfStatement,
|
|
1528
|
+
SyntaxKind.ForStatement,
|
|
1529
|
+
SyntaxKind.ForInStatement,
|
|
1530
|
+
SyntaxKind.ForOfStatement,
|
|
1531
|
+
SyntaxKind.WhileStatement,
|
|
1532
|
+
SyntaxKind.DoStatement,
|
|
1533
|
+
SyntaxKind.CaseClause,
|
|
1534
|
+
SyntaxKind.CatchClause,
|
|
1535
|
+
SyntaxKind.ConditionalExpression
|
|
1536
|
+
]);
|
|
1537
|
+
/* @__PURE__ */ new Set([
|
|
1538
|
+
SyntaxKind.AmpersandAmpersandToken,
|
|
1539
|
+
// &&
|
|
1540
|
+
SyntaxKind.BarBarToken,
|
|
1541
|
+
// ||
|
|
1542
|
+
SyntaxKind.QuestionQuestionToken
|
|
1543
|
+
// ??
|
|
1544
|
+
]);
|
|
1545
|
+
|
|
1546
|
+
// src/config/detect.ts
|
|
1547
|
+
function detectFeatures(projectRoot) {
|
|
1548
|
+
const pkgPath = path12.join(projectRoot, "package.json");
|
|
1549
|
+
const pkg = readJson(pkgPath);
|
|
1550
|
+
if (!pkg) {
|
|
1551
|
+
return {};
|
|
1552
|
+
}
|
|
1553
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1554
|
+
const hasWorkspaces = Boolean(pkg.workspaces);
|
|
1555
|
+
return {
|
|
1556
|
+
prisma: "@prisma/client" in allDeps || "prisma" in allDeps,
|
|
1557
|
+
trpc: "@trpc/server" in allDeps || "@trpc/client" in allDeps,
|
|
1558
|
+
nextjs: "next" in allDeps,
|
|
1559
|
+
react: "react" in allDeps,
|
|
1560
|
+
monorepo: hasWorkspaces || exists(path12.join(projectRoot, "pnpm-workspace.yaml")),
|
|
1561
|
+
typescript: "typescript" in allDeps || exists(path12.join(projectRoot, "tsconfig.json"))
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
function detectPaths(projectRoot, isMonorepo) {
|
|
1565
|
+
const detected = {};
|
|
1566
|
+
if (isMonorepo) {
|
|
1567
|
+
const monorepoPatterns = [
|
|
1568
|
+
{ web: "apps/web", api: "packages/api", db: "packages/db", shared: "packages/shared" },
|
|
1569
|
+
{ web: "packages/web", api: "packages/api", db: "packages/db", shared: "packages/shared" },
|
|
1570
|
+
{
|
|
1571
|
+
web: "apps/frontend",
|
|
1572
|
+
api: "apps/backend",
|
|
1573
|
+
db: "packages/database",
|
|
1574
|
+
shared: "packages/common"
|
|
1575
|
+
}
|
|
1576
|
+
];
|
|
1577
|
+
for (const pattern of monorepoPatterns) {
|
|
1578
|
+
if (exists(path12.join(projectRoot, pattern.web))) {
|
|
1579
|
+
detected.web = pattern.web;
|
|
1580
|
+
detected.api = pattern.api;
|
|
1581
|
+
detected.db = pattern.db;
|
|
1582
|
+
detected.shared = pattern.shared;
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
} else {
|
|
1587
|
+
if (exists(path12.join(projectRoot, "src"))) {
|
|
1588
|
+
detected.web = "src";
|
|
1589
|
+
detected.lib = "src/lib";
|
|
1590
|
+
detected.components = "src/components";
|
|
1591
|
+
detected.hooks = "src/hooks";
|
|
1592
|
+
}
|
|
1593
|
+
if (exists(path12.join(projectRoot, "app"))) {
|
|
1594
|
+
detected.web = "app";
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return detected;
|
|
1598
|
+
}
|
|
1599
|
+
function detectPrisma(projectRoot, _webPath) {
|
|
1600
|
+
const detected = {};
|
|
1601
|
+
const multiSchemaPath = path12.join(projectRoot, "prisma/schema");
|
|
1602
|
+
if (isDirectory(multiSchemaPath)) {
|
|
1603
|
+
detected.schemaDir = "prisma/schema";
|
|
1604
|
+
return detected;
|
|
1605
|
+
}
|
|
1606
|
+
const singleSchemaPath = path12.join(projectRoot, "prisma/schema.prisma");
|
|
1607
|
+
if (exists(singleSchemaPath)) {
|
|
1608
|
+
detected.schemaDir = "prisma/schema.prisma";
|
|
1609
|
+
return detected;
|
|
1610
|
+
}
|
|
1611
|
+
const pkgDbSchema = path12.join(projectRoot, "packages/db/prisma/schema");
|
|
1612
|
+
if (isDirectory(pkgDbSchema)) {
|
|
1613
|
+
detected.schemaDir = "packages/db/prisma/schema";
|
|
1614
|
+
return detected;
|
|
1615
|
+
}
|
|
1616
|
+
const pkgDbSingleSchema = path12.join(projectRoot, "packages/db/prisma/schema.prisma");
|
|
1617
|
+
if (exists(pkgDbSingleSchema)) {
|
|
1618
|
+
detected.schemaDir = "packages/db/prisma/schema.prisma";
|
|
1619
|
+
return detected;
|
|
1620
|
+
}
|
|
1621
|
+
return detected;
|
|
1622
|
+
}
|
|
1623
|
+
function detectTrpc(projectRoot, _apiPath) {
|
|
1624
|
+
const detected = {};
|
|
1625
|
+
const routerPatterns = [
|
|
1626
|
+
"src/server/routers",
|
|
1627
|
+
"src/trpc/routers",
|
|
1628
|
+
"server/routers",
|
|
1629
|
+
"packages/api/src/routers",
|
|
1630
|
+
"apps/api/src/routers"
|
|
1631
|
+
];
|
|
1632
|
+
for (const pattern of routerPatterns) {
|
|
1633
|
+
const fullPath = path12.join(projectRoot, pattern);
|
|
1634
|
+
if (isDirectory(fullPath)) {
|
|
1635
|
+
detected.routersDir = pattern;
|
|
1636
|
+
const indexPath = path12.join(fullPath, "index.ts");
|
|
1637
|
+
if (exists(indexPath)) {
|
|
1638
|
+
detected.appRouter = `${pattern}/index.ts`;
|
|
1639
|
+
}
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return detected;
|
|
1644
|
+
}
|
|
1645
|
+
function detectProjectName(projectRoot) {
|
|
1646
|
+
const pkgPath = path12.join(projectRoot, "package.json");
|
|
1647
|
+
const pkg = readJson(pkgPath);
|
|
1648
|
+
return pkg?.name;
|
|
1649
|
+
}
|
|
1650
|
+
function detectMonorepoPackages(projectRoot) {
|
|
1651
|
+
const packages = [];
|
|
1652
|
+
const libPatterns = ["lib", "src/lib"];
|
|
1653
|
+
const appsDir = path12.join(projectRoot, "apps");
|
|
1654
|
+
if (isDirectory(appsDir)) {
|
|
1655
|
+
const apps = fs6.readdirSync(appsDir).filter((name) => {
|
|
1656
|
+
const fullPath = path12.join(appsDir, name);
|
|
1657
|
+
return isDirectory(fullPath) && !name.startsWith(".");
|
|
1658
|
+
});
|
|
1659
|
+
for (const app of apps) {
|
|
1660
|
+
const appPath = path12.join("apps", app);
|
|
1661
|
+
for (const libPattern of libPatterns) {
|
|
1662
|
+
const libPath = path12.join(appPath, libPattern);
|
|
1663
|
+
const fullLibPath = path12.join(projectRoot, libPath);
|
|
1664
|
+
if (isDirectory(fullLibPath)) {
|
|
1665
|
+
let tsconfigPath = path12.join(appPath, "tsconfig.json");
|
|
1666
|
+
if (!exists(path12.join(projectRoot, tsconfigPath))) {
|
|
1667
|
+
tsconfigPath = "tsconfig.base.json";
|
|
1668
|
+
}
|
|
1669
|
+
packages.push({
|
|
1670
|
+
name: app,
|
|
1671
|
+
path: appPath,
|
|
1672
|
+
libPath,
|
|
1673
|
+
tsconfigPath,
|
|
1674
|
+
type: "app"
|
|
1675
|
+
});
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const packagesDir = path12.join(projectRoot, "packages");
|
|
1682
|
+
if (isDirectory(packagesDir)) {
|
|
1683
|
+
const pkgs = fs6.readdirSync(packagesDir).filter((name) => {
|
|
1684
|
+
const fullPath = path12.join(packagesDir, name);
|
|
1685
|
+
return isDirectory(fullPath) && !name.startsWith(".");
|
|
1686
|
+
});
|
|
1687
|
+
for (const pkg of pkgs) {
|
|
1688
|
+
const pkgPath = path12.join("packages", pkg);
|
|
1689
|
+
for (const libPattern of libPatterns) {
|
|
1690
|
+
const libPath = path12.join(pkgPath, libPattern);
|
|
1691
|
+
const fullLibPath = path12.join(projectRoot, libPath);
|
|
1692
|
+
if (isDirectory(fullLibPath)) {
|
|
1693
|
+
let tsconfigPath = path12.join(pkgPath, "tsconfig.json");
|
|
1694
|
+
if (!exists(path12.join(projectRoot, tsconfigPath))) {
|
|
1695
|
+
tsconfigPath = "tsconfig.base.json";
|
|
1696
|
+
}
|
|
1697
|
+
packages.push({
|
|
1698
|
+
name: pkg,
|
|
1699
|
+
path: pkgPath,
|
|
1700
|
+
libPath,
|
|
1701
|
+
tsconfigPath,
|
|
1702
|
+
type: "package"
|
|
1703
|
+
});
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
return packages;
|
|
1710
|
+
}
|
|
1711
|
+
function detectAll(projectRoot) {
|
|
1712
|
+
const features = detectFeatures(projectRoot);
|
|
1713
|
+
const paths = detectPaths(projectRoot, features.monorepo ?? false);
|
|
1714
|
+
const prisma = features.prisma ? detectPrisma(projectRoot, paths.web) : {};
|
|
1715
|
+
const trpc = features.trpc ? detectTrpc(projectRoot, paths.api) : {};
|
|
1716
|
+
const name = detectProjectName(projectRoot);
|
|
1717
|
+
return {
|
|
1718
|
+
name,
|
|
1719
|
+
features,
|
|
1720
|
+
paths,
|
|
1721
|
+
prisma,
|
|
1722
|
+
trpc
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/config/domains.ts
|
|
1727
|
+
var DOMAIN_KEYWORDS = {
|
|
1728
|
+
booking: [
|
|
1729
|
+
"booking",
|
|
1730
|
+
"slot",
|
|
1731
|
+
"availability",
|
|
1732
|
+
"schedule",
|
|
1733
|
+
"appointment",
|
|
1734
|
+
"reservation",
|
|
1735
|
+
"calendar",
|
|
1736
|
+
"time slot",
|
|
1737
|
+
"bookingSettings",
|
|
1738
|
+
"busySlot"
|
|
1739
|
+
],
|
|
1740
|
+
events: ["events", "event", "ticket", "ticketing", "venue", "concert", "festival"],
|
|
1741
|
+
places: ["places", "place", "business", "location", "restaurant", "cafe", "bar", "club"],
|
|
1742
|
+
users: ["users", "user", "profile", "account", "auth", "login", "registration"],
|
|
1743
|
+
crm: ["crm", "customer", "client", "lead", "contact", "interaction", "customerNote"],
|
|
1744
|
+
gamification: [
|
|
1745
|
+
"gamification",
|
|
1746
|
+
"reward",
|
|
1747
|
+
"points",
|
|
1748
|
+
"achievement",
|
|
1749
|
+
"badge",
|
|
1750
|
+
"level",
|
|
1751
|
+
"leaderboard",
|
|
1752
|
+
"streak"
|
|
1753
|
+
],
|
|
1754
|
+
payments: [
|
|
1755
|
+
"payment",
|
|
1756
|
+
"subscription",
|
|
1757
|
+
"invoice",
|
|
1758
|
+
"billing",
|
|
1759
|
+
"transaction",
|
|
1760
|
+
"refund",
|
|
1761
|
+
"payout",
|
|
1762
|
+
"stripe"
|
|
1763
|
+
],
|
|
1764
|
+
notifications: [
|
|
1765
|
+
"notification",
|
|
1766
|
+
"email",
|
|
1767
|
+
"sms",
|
|
1768
|
+
"push",
|
|
1769
|
+
"reminder",
|
|
1770
|
+
"alert",
|
|
1771
|
+
"message",
|
|
1772
|
+
"template"
|
|
1773
|
+
],
|
|
1774
|
+
admin: [
|
|
1775
|
+
"admin",
|
|
1776
|
+
"dashboard",
|
|
1777
|
+
"analytics",
|
|
1778
|
+
"moderation",
|
|
1779
|
+
"report",
|
|
1780
|
+
"stats",
|
|
1781
|
+
"metrics",
|
|
1782
|
+
"management"
|
|
1783
|
+
],
|
|
1784
|
+
mobile: ["mobile", "expo", "react native", "app", "ios", "android", "push notification"]
|
|
1785
|
+
};
|
|
1786
|
+
var DOMAIN_FILES = {
|
|
1787
|
+
booking: [
|
|
1788
|
+
"packages/api/src/routers/booking*.ts",
|
|
1789
|
+
"packages/api/src/routers/business/booking*.ts",
|
|
1790
|
+
"apps/web/components/Business/Booking/**/*.tsx",
|
|
1791
|
+
"apps/web/components/booking/**/*.tsx",
|
|
1792
|
+
"packages/db/prisma/models/booking*.prisma",
|
|
1793
|
+
"packages/shared/src/schemas/booking*.ts"
|
|
1794
|
+
],
|
|
1795
|
+
events: [
|
|
1796
|
+
"packages/api/src/routers/events*.ts",
|
|
1797
|
+
"packages/api/src/routers/business/ticketing*.ts",
|
|
1798
|
+
"apps/web/components/Business/Ticketing/**/*.tsx",
|
|
1799
|
+
"apps/web/components/events/**/*.tsx",
|
|
1800
|
+
"packages/db/prisma/models/event*.prisma",
|
|
1801
|
+
"packages/db/prisma/models/ticket*.prisma",
|
|
1802
|
+
"packages/shared/src/schemas/event*.ts"
|
|
1803
|
+
],
|
|
1804
|
+
places: [
|
|
1805
|
+
"packages/api/src/routers/places*.ts",
|
|
1806
|
+
"packages/api/src/routers/business*.ts",
|
|
1807
|
+
"apps/web/components/Business/**/*.tsx",
|
|
1808
|
+
"apps/web/components/places/**/*.tsx",
|
|
1809
|
+
"packages/db/prisma/models/place*.prisma",
|
|
1810
|
+
"packages/db/prisma/models/business*.prisma",
|
|
1811
|
+
"packages/shared/src/schemas/place*.ts"
|
|
1812
|
+
],
|
|
1813
|
+
users: [
|
|
1814
|
+
"packages/api/src/routers/user*.ts",
|
|
1815
|
+
"packages/api/src/routers/auth*.ts",
|
|
1816
|
+
"apps/web/components/auth/**/*.tsx",
|
|
1817
|
+
"apps/web/components/profile/**/*.tsx",
|
|
1818
|
+
"packages/db/prisma/models/user*.prisma",
|
|
1819
|
+
"packages/shared/src/schemas/user*.ts"
|
|
1820
|
+
],
|
|
1821
|
+
crm: [
|
|
1822
|
+
"packages/api/src/routers/business/crm*.ts",
|
|
1823
|
+
"packages/api/src/routers/customer*.ts",
|
|
1824
|
+
"apps/web/components/Business/CRM/**/*.tsx",
|
|
1825
|
+
"packages/db/prisma/models/customer*.prisma",
|
|
1826
|
+
"packages/shared/src/schemas/customer*.ts"
|
|
1827
|
+
],
|
|
1828
|
+
gamification: [
|
|
1829
|
+
"packages/api/src/routers/gamification*.ts",
|
|
1830
|
+
"apps/web/components/gamification/**/*.tsx",
|
|
1831
|
+
"packages/db/prisma/models/gamification*.prisma",
|
|
1832
|
+
"packages/shared/src/schemas/gamification*.ts"
|
|
1833
|
+
],
|
|
1834
|
+
payments: [
|
|
1835
|
+
"packages/api/src/routers/payment*.ts",
|
|
1836
|
+
"packages/api/src/routers/subscription*.ts",
|
|
1837
|
+
"apps/web/components/payments/**/*.tsx",
|
|
1838
|
+
"packages/db/prisma/models/payment*.prisma",
|
|
1839
|
+
"packages/db/prisma/models/subscription*.prisma"
|
|
1840
|
+
],
|
|
1841
|
+
notifications: [
|
|
1842
|
+
"packages/api/src/routers/notification*.ts",
|
|
1843
|
+
"apps/web/components/notifications/**/*.tsx",
|
|
1844
|
+
"packages/db/prisma/models/notification*.prisma"
|
|
1845
|
+
],
|
|
1846
|
+
admin: [
|
|
1847
|
+
"packages/api/src/routers/admin*.ts",
|
|
1848
|
+
"apps/web/components/admin/**/*.tsx",
|
|
1849
|
+
"apps/web/app/admin/**/*.tsx"
|
|
1850
|
+
],
|
|
1851
|
+
mobile: ["apps/mobile/**/*.tsx", "apps/mobile/**/*.ts", "packages/shared/src/mobile/**/*.ts"]
|
|
1852
|
+
};
|
|
1853
|
+
var DOMAIN_APPROACHES = {
|
|
1854
|
+
booking: [
|
|
1855
|
+
"Check existing Booking schema in packages/db/prisma/models/",
|
|
1856
|
+
"Review tRPC router at packages/api/src/routers/",
|
|
1857
|
+
"Update Zod schemas in packages/shared/",
|
|
1858
|
+
"Create/update React components in apps/web/components/Business/Booking/",
|
|
1859
|
+
"Add tests for new booking logic"
|
|
1860
|
+
],
|
|
1861
|
+
events: [
|
|
1862
|
+
"Check Event/Ticket schemas in packages/db/prisma/models/",
|
|
1863
|
+
"Review tRPC router for events/ticketing",
|
|
1864
|
+
"Update Zod schemas in packages/shared/",
|
|
1865
|
+
"Create/update React components in apps/web/components/Business/Ticketing/",
|
|
1866
|
+
"Consider event occurrence patterns"
|
|
1867
|
+
],
|
|
1868
|
+
places: [
|
|
1869
|
+
"Check Place/Business schemas in packages/db/prisma/models/",
|
|
1870
|
+
"Review tRPC router for places/business",
|
|
1871
|
+
"Update Zod schemas in packages/shared/",
|
|
1872
|
+
"Consider business hours and availability"
|
|
1873
|
+
],
|
|
1874
|
+
users: [
|
|
1875
|
+
"Check User schema in packages/db/prisma/models/",
|
|
1876
|
+
"Review auth router in packages/api/",
|
|
1877
|
+
"Update authentication middleware if needed",
|
|
1878
|
+
"Consider email verification flow"
|
|
1879
|
+
],
|
|
1880
|
+
crm: [
|
|
1881
|
+
"Check Customer schema in packages/db/prisma/models/",
|
|
1882
|
+
"Review CRM router in packages/api/src/routers/business/",
|
|
1883
|
+
"Update customer interaction tracking",
|
|
1884
|
+
"Consider segmentation logic"
|
|
1885
|
+
],
|
|
1886
|
+
gamification: [
|
|
1887
|
+
"Check Gamification schema in packages/db/prisma/models/",
|
|
1888
|
+
"Review points calculation logic",
|
|
1889
|
+
"Update achievement triggers",
|
|
1890
|
+
"Consider leaderboard queries performance"
|
|
1891
|
+
],
|
|
1892
|
+
payments: [
|
|
1893
|
+
"Check Payment/Subscription schemas",
|
|
1894
|
+
"Review Stripe integration",
|
|
1895
|
+
"Update webhook handlers",
|
|
1896
|
+
"Consider refund flow"
|
|
1897
|
+
],
|
|
1898
|
+
notifications: [
|
|
1899
|
+
"Check Notification schema",
|
|
1900
|
+
"Review email/SMS templates",
|
|
1901
|
+
"Update notification triggers",
|
|
1902
|
+
"Consider push notification setup"
|
|
1903
|
+
],
|
|
1904
|
+
admin: [
|
|
1905
|
+
"Check admin permissions",
|
|
1906
|
+
"Review dashboard queries for performance",
|
|
1907
|
+
"Update analytics aggregations",
|
|
1908
|
+
"Consider data export functionality"
|
|
1909
|
+
],
|
|
1910
|
+
mobile: [
|
|
1911
|
+
"Check Expo setup in apps/mobile/",
|
|
1912
|
+
"Review shared components from packages/shared/",
|
|
1913
|
+
"Update navigation structure",
|
|
1914
|
+
"Consider offline support"
|
|
1915
|
+
]
|
|
1916
|
+
};
|
|
1917
|
+
function detectDomains(text) {
|
|
1918
|
+
const lowerText = text.toLowerCase();
|
|
1919
|
+
const detected = [];
|
|
1920
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
|
|
1921
|
+
for (const keyword of keywords) {
|
|
1922
|
+
if (lowerText.includes(keyword.toLowerCase())) {
|
|
1923
|
+
if (!detected.includes(domain)) {
|
|
1924
|
+
detected.push(domain);
|
|
1925
|
+
}
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return detected;
|
|
1931
|
+
}
|
|
1932
|
+
function getRelatedFiles(domains) {
|
|
1933
|
+
const files = [];
|
|
1934
|
+
for (const domain of domains) {
|
|
1935
|
+
const patterns = DOMAIN_FILES[domain];
|
|
1936
|
+
if (patterns) {
|
|
1937
|
+
files.push(...patterns);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
return [...new Set(files)];
|
|
1941
|
+
}
|
|
1942
|
+
function getApproaches(domains) {
|
|
1943
|
+
const approaches = [];
|
|
1944
|
+
for (const domain of domains) {
|
|
1945
|
+
const steps = DOMAIN_APPROACHES[domain];
|
|
1946
|
+
if (steps) {
|
|
1947
|
+
approaches.push(...steps);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
return [...new Set(approaches)];
|
|
1951
|
+
}
|
|
1952
|
+
var CONFIG_MODULE_NAME = "krolik";
|
|
1953
|
+
var cachedConfig = null;
|
|
1954
|
+
var cachedProjectRoot = null;
|
|
1955
|
+
function findProjectRoot2(startDir = process.cwd()) {
|
|
1956
|
+
let currentDir = startDir;
|
|
1957
|
+
while (currentDir !== path12.dirname(currentDir)) {
|
|
1958
|
+
const pkgPath = path12.join(currentDir, "package.json");
|
|
1959
|
+
try {
|
|
1960
|
+
const fs11 = __require("fs");
|
|
1961
|
+
if (fs11.existsSync(pkgPath)) {
|
|
1962
|
+
return currentDir;
|
|
1963
|
+
}
|
|
1964
|
+
} catch {
|
|
1965
|
+
}
|
|
1966
|
+
currentDir = path12.dirname(currentDir);
|
|
1967
|
+
}
|
|
1968
|
+
return startDir;
|
|
1969
|
+
}
|
|
1970
|
+
async function loadConfigFile(projectRoot) {
|
|
1971
|
+
const explorer = cosmiconfig(CONFIG_MODULE_NAME, {
|
|
1972
|
+
searchPlaces: [
|
|
1973
|
+
"krolik.config.ts",
|
|
1974
|
+
"krolik.config.js",
|
|
1975
|
+
"krolik.config.mjs",
|
|
1976
|
+
"krolik.config.json",
|
|
1977
|
+
"krolik.config.yaml",
|
|
1978
|
+
"krolik.config.yml",
|
|
1979
|
+
"krolik.yaml",
|
|
1980
|
+
"krolik.yml",
|
|
1981
|
+
".krolikrc",
|
|
1982
|
+
".krolikrc.json",
|
|
1983
|
+
".krolikrc.yaml",
|
|
1984
|
+
".krolikrc.yml",
|
|
1985
|
+
".krolikrc.js",
|
|
1986
|
+
".krolikrc.ts"
|
|
1987
|
+
]
|
|
1988
|
+
});
|
|
1989
|
+
try {
|
|
1990
|
+
const result = await explorer.search(projectRoot);
|
|
1991
|
+
return result?.config ?? null;
|
|
1992
|
+
} catch {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
function resolveConfig(userConfig, detected, projectRoot) {
|
|
1997
|
+
const base = createDefaultConfig(projectRoot);
|
|
1998
|
+
const resolvedPaths = {
|
|
1999
|
+
...base.paths,
|
|
2000
|
+
...detected.paths,
|
|
2001
|
+
...userConfig?.paths
|
|
2002
|
+
};
|
|
2003
|
+
const resolvedFeatures = {
|
|
2004
|
+
...base.features,
|
|
2005
|
+
...detected.features,
|
|
2006
|
+
...userConfig?.features
|
|
2007
|
+
};
|
|
2008
|
+
const resolvedPrisma = {
|
|
2009
|
+
...base.prisma,
|
|
2010
|
+
...detected.prisma,
|
|
2011
|
+
...userConfig?.prisma
|
|
2012
|
+
};
|
|
2013
|
+
const resolvedTrpc = {
|
|
2014
|
+
...base.trpc,
|
|
2015
|
+
...detected.trpc,
|
|
2016
|
+
...userConfig?.trpc
|
|
2017
|
+
};
|
|
2018
|
+
return {
|
|
2019
|
+
name: userConfig?.name ?? detected.name ?? base.name,
|
|
2020
|
+
projectRoot: userConfig?.projectRoot ?? projectRoot,
|
|
2021
|
+
paths: resolvedPaths,
|
|
2022
|
+
features: resolvedFeatures,
|
|
2023
|
+
prisma: resolvedPrisma,
|
|
2024
|
+
trpc: resolvedTrpc,
|
|
2025
|
+
templates: { ...base.templates, ...userConfig?.templates },
|
|
2026
|
+
exclude: userConfig?.exclude ?? base.exclude,
|
|
2027
|
+
extensions: userConfig?.extensions ?? base.extensions
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
async function loadConfig(options = {}) {
|
|
2031
|
+
const { projectRoot: explicitRoot, noCache = false } = options;
|
|
2032
|
+
const projectRoot = explicitRoot ?? findProjectRoot2();
|
|
2033
|
+
if (!noCache && cachedConfig && cachedProjectRoot === projectRoot) {
|
|
2034
|
+
return cachedConfig;
|
|
2035
|
+
}
|
|
2036
|
+
const userConfig = await loadConfigFile(projectRoot);
|
|
2037
|
+
const detected = detectAll(projectRoot);
|
|
2038
|
+
const resolved = resolveConfig(userConfig, detected, projectRoot);
|
|
2039
|
+
cachedConfig = resolved;
|
|
2040
|
+
cachedProjectRoot = projectRoot;
|
|
2041
|
+
return resolved;
|
|
2042
|
+
}
|
|
2043
|
+
function getConfig() {
|
|
2044
|
+
if (!cachedConfig) {
|
|
2045
|
+
const projectRoot = findProjectRoot2();
|
|
2046
|
+
const detected = detectAll(projectRoot);
|
|
2047
|
+
return resolveConfig(null, detected, projectRoot);
|
|
2048
|
+
}
|
|
2049
|
+
return cachedConfig;
|
|
2050
|
+
}
|
|
2051
|
+
function clearConfigCache() {
|
|
2052
|
+
cachedConfig = null;
|
|
2053
|
+
cachedProjectRoot = null;
|
|
2054
|
+
}
|
|
2055
|
+
function defineConfig(config) {
|
|
2056
|
+
return config;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
export { DEFAULT_EXCLUDE, DEFAULT_EXTENSIONS, DEFAULT_FEATURES, DEFAULT_PATHS, DOMAIN_APPROACHES, DOMAIN_FILES, DOMAIN_KEYWORDS, clearConfigCache, createDefaultConfig, createLogger, defineConfig, detectAll, detectDomains, detectFeatures, detectMonorepoPackages, detectPaths, detectPrisma, detectProjectName, detectTrpc, exec, execLines, findFiles, findProjectRoot2 as findProjectRoot, getApproaches, getConfig, getCurrentBranch, getRelatedFiles, getStatus, isGitRepo, loadConfig, readFile, tryExec, writeFile };
|
|
2060
|
+
//# sourceMappingURL=chunk-DTDOBWO6.js.map
|
|
2061
|
+
//# sourceMappingURL=chunk-DTDOBWO6.js.map
|