project-context-ai 2.2.5 → 2.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/dist/generators/template-sections.d.ts +8 -0
- package/dist/generators/template-sections.d.ts.map +1 -0
- package/dist/generators/template-sections.js +261 -0
- package/dist/generators/template-sections.js.map +1 -0
- package/dist/generators/template.d.ts +1 -6
- package/dist/generators/template.d.ts.map +1 -1
- package/dist/generators/template.js +2 -280
- package/dist/generators/template.js.map +1 -1
- package/dist/scanner/business.d.ts.map +1 -1
- package/dist/scanner/business.js +12 -726
- package/dist/scanner/business.js.map +1 -1
- package/dist/scanner/conventions.js +25 -6
- package/dist/scanner/conventions.js.map +1 -1
- package/dist/scanner/dependencies.d.ts.map +1 -1
- package/dist/scanner/dependencies.js +30 -0
- package/dist/scanner/dependencies.js.map +1 -1
- package/dist/scanner/description.d.ts +7 -0
- package/dist/scanner/description.d.ts.map +1 -0
- package/dist/scanner/description.js +172 -0
- package/dist/scanner/description.js.map +1 -0
- package/dist/scanner/domain.d.ts +10 -0
- package/dist/scanner/domain.d.ts.map +1 -0
- package/dist/scanner/domain.js +323 -0
- package/dist/scanner/domain.js.map +1 -0
- package/dist/scanner/frameworks.d.ts.map +1 -1
- package/dist/scanner/frameworks.js +13 -0
- package/dist/scanner/frameworks.js.map +1 -1
- package/dist/scanner/patterns-code.d.ts +9 -0
- package/dist/scanner/patterns-code.d.ts.map +1 -0
- package/dist/scanner/patterns-code.js +283 -0
- package/dist/scanner/patterns-code.js.map +1 -0
- package/dist/scanner/patterns.d.ts.map +1 -1
- package/dist/scanner/patterns.js +2 -288
- package/dist/scanner/patterns.js.map +1 -1
- package/dist/scanner/skills.d.ts +5 -0
- package/dist/scanner/skills.d.ts.map +1 -0
- package/dist/scanner/skills.js +242 -0
- package/dist/scanner/skills.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// ─── Code Detection Patterns (Error, Test, Architecture, Examples, Workflows, Conventions)
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readTextFile, readFilesMatching } from "../utils/files.js";
|
|
4
|
+
import { getScanConfig } from "./config.js";
|
|
5
|
+
// ─── Error Handling ───────────────────────────────────────
|
|
6
|
+
export async function detectErrorHandling(rootPath, allFiles) {
|
|
7
|
+
const patterns = [];
|
|
8
|
+
const cfg = getScanConfig();
|
|
9
|
+
const sourceFiles = await readFilesMatching(rootPath, allFiles, {
|
|
10
|
+
extensions: [".ts", ".js"],
|
|
11
|
+
maxFiles: cfg.maxFiles,
|
|
12
|
+
maxFileSize: cfg.maxFileSize,
|
|
13
|
+
});
|
|
14
|
+
for (const { file, content } of sourceFiles) {
|
|
15
|
+
const regex = /class\s+(\w+)\s+extends\s+(Error|BaseError|DomainError|AppError|HttpException|CustomError|BusinessError|NotFound|BadRequest|Unauthorized|Forbidden)\b/g;
|
|
16
|
+
let match;
|
|
17
|
+
while ((match = regex.exec(content)) !== null) {
|
|
18
|
+
patterns.push({ className: match[1], extendsFrom: match[2], file });
|
|
19
|
+
}
|
|
20
|
+
if (patterns.length >= 30)
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
return patterns.filter((p) => {
|
|
25
|
+
if (seen.has(p.className))
|
|
26
|
+
return false;
|
|
27
|
+
seen.add(p.className);
|
|
28
|
+
return true;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// ─── Test Patterns ────────────────────────────────────────
|
|
32
|
+
export async function detectTestPatterns(rootPath, allFiles, deps, frameworks) {
|
|
33
|
+
const depNames = deps.map((d) => d.name);
|
|
34
|
+
const fwNames = frameworks.map((f) => f.name);
|
|
35
|
+
let framework = "";
|
|
36
|
+
if (depNames.includes("vitest") || fwNames.includes("Vitest"))
|
|
37
|
+
framework = "Vitest";
|
|
38
|
+
else if (depNames.includes("jest") || depNames.includes("ts-jest") || fwNames.includes("Jest"))
|
|
39
|
+
framework = "Jest";
|
|
40
|
+
else if (depNames.includes("mocha"))
|
|
41
|
+
framework = "Mocha";
|
|
42
|
+
if (!framework)
|
|
43
|
+
return null;
|
|
44
|
+
let coverageThreshold = null;
|
|
45
|
+
const configFiles = allFiles.filter((f) => f.match(/(jest|vitest)\..*config\.(ts|js|mjs|cjs|json)$/));
|
|
46
|
+
for (const cf of configFiles.slice(0, 3)) {
|
|
47
|
+
const content = await readTextFile(join(rootPath, cf));
|
|
48
|
+
if (!content)
|
|
49
|
+
continue;
|
|
50
|
+
const thresholdMatch = content.match(/(?:branches|lines|functions|statements)\s*:\s*(\d+)/);
|
|
51
|
+
if (thresholdMatch) {
|
|
52
|
+
coverageThreshold = parseInt(thresholdMatch[1], 10);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const testFiles = allFiles.filter((f) => f.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/));
|
|
57
|
+
const specCount = testFiles.filter((f) => f.includes(".spec.")).length;
|
|
58
|
+
const testCount = testFiles.filter((f) => f.includes(".test.")).length;
|
|
59
|
+
const unitFilePattern = specCount > testCount ? "*.spec.ts" : "*.test.ts";
|
|
60
|
+
let integrationFilePattern = null;
|
|
61
|
+
if (allFiles.some((f) => f.includes("__tests__/")))
|
|
62
|
+
integrationFilePattern = "__tests__/**/*.test.ts";
|
|
63
|
+
else if (allFiles.some((f) => f.includes("e2e/")))
|
|
64
|
+
integrationFilePattern = "e2e/**/*.test.ts";
|
|
65
|
+
let testDir = "src/";
|
|
66
|
+
if (allFiles.some((f) => f.includes("__tests__/")))
|
|
67
|
+
testDir = "__tests__/";
|
|
68
|
+
else if (allFiles.some((f) => f.match(/^tests?\//)))
|
|
69
|
+
testDir = "tests/";
|
|
70
|
+
let assertionStyle = null;
|
|
71
|
+
if (testFiles.length > 0) {
|
|
72
|
+
const content = await readTextFile(join(rootPath, testFiles[0]));
|
|
73
|
+
if (content) {
|
|
74
|
+
if (content.includes("expect("))
|
|
75
|
+
assertionStyle = "expect() (Jest/Vitest)";
|
|
76
|
+
else if (content.includes("assert."))
|
|
77
|
+
assertionStyle = "assert (Node.js/Chai)";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { framework, coverageThreshold, unitFilePattern, integrationFilePattern, testDir, assertionStyle };
|
|
81
|
+
}
|
|
82
|
+
// ─── Architectural Rules ──────────────────────────────────
|
|
83
|
+
export async function detectArchitecturalRules(rootPath, allFiles) {
|
|
84
|
+
const rules = [];
|
|
85
|
+
const dcFile = allFiles.find((f) => f.match(/\.dependency-cruiser\.(cjs|js|json)$/));
|
|
86
|
+
if (dcFile) {
|
|
87
|
+
const content = await readTextFile(join(rootPath, dcFile));
|
|
88
|
+
if (content) {
|
|
89
|
+
const nameRegex = /name\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
90
|
+
let match;
|
|
91
|
+
while ((match = nameRegex.exec(content)) !== null) {
|
|
92
|
+
rules.push(`Dependency rule: ${match[1]}`);
|
|
93
|
+
if (rules.length >= 10)
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const eslintFiles = allFiles.filter((f) => f.match(/eslint/i) && !f.includes("node_modules"));
|
|
99
|
+
for (const ef of eslintFiles.slice(0, 2)) {
|
|
100
|
+
const content = await readTextFile(join(rootPath, ef));
|
|
101
|
+
if (!content)
|
|
102
|
+
continue;
|
|
103
|
+
if (content.includes("import/no-restricted-paths") || content.includes("boundaries/element-types")) {
|
|
104
|
+
rules.push("ESLint enforced import boundaries between layers");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const hasDomain = allFiles.some((f) => f.match(/src\/(domain|core)\//));
|
|
108
|
+
const hasInfra = allFiles.some((f) => f.match(/src\/(infra|infrastructure)\//));
|
|
109
|
+
const hasApp = allFiles.some((f) => f.match(/src\/(application|use-?cases)\//));
|
|
110
|
+
if (hasDomain && hasInfra) {
|
|
111
|
+
const cfg = getScanConfig();
|
|
112
|
+
const domainFiles = await readFilesMatching(rootPath, allFiles, {
|
|
113
|
+
extensions: [".ts", ".js"],
|
|
114
|
+
maxFiles: cfg.maxFiles,
|
|
115
|
+
maxFileSize: cfg.maxFileSize,
|
|
116
|
+
pathPatterns: [/src\/(domain|core)\//],
|
|
117
|
+
});
|
|
118
|
+
let domainImportsInfra = false;
|
|
119
|
+
for (const { content } of domainFiles) {
|
|
120
|
+
if (content.match(/from\s+['"].*\/(infra|infrastructure)\//)) {
|
|
121
|
+
domainImportsInfra = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!domainImportsInfra) {
|
|
126
|
+
rules.push("Domain/Core layer does NOT import from Infrastructure (Clean Architecture)");
|
|
127
|
+
}
|
|
128
|
+
if (hasApp) {
|
|
129
|
+
rules.push("Layers: Domain → Application → Infrastructure");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return rules;
|
|
133
|
+
}
|
|
134
|
+
// ─── Code Examples ────────────────────────────────────────
|
|
135
|
+
export async function extractCodeExamples(rootPath, allFiles, deps) {
|
|
136
|
+
const examples = [];
|
|
137
|
+
const depNames = deps.map((d) => d.name);
|
|
138
|
+
const categories = [
|
|
139
|
+
{ category: "service", patterns: [/services?\//i], label: "Service pattern" },
|
|
140
|
+
{ category: "controller", patterns: [/controllers?\//i], label: "Controller pattern" },
|
|
141
|
+
{ category: "repository", patterns: [/repositor/i], label: "Repository pattern" },
|
|
142
|
+
{ category: "middleware", patterns: [/middleware/i], label: "Middleware pattern" },
|
|
143
|
+
{ category: "error-handling", patterns: [/(errors|exceptions)\//i], label: "Error handling" },
|
|
144
|
+
];
|
|
145
|
+
for (const cat of categories) {
|
|
146
|
+
const candidates = allFiles.filter((f) => f.match(/\.(ts|js)$/) &&
|
|
147
|
+
cat.patterns.some((p) => p.test(f)) &&
|
|
148
|
+
!f.includes(".test.") && !f.includes(".spec.") &&
|
|
149
|
+
!f.includes("index.") && !f.includes("generated/"));
|
|
150
|
+
if (candidates.length === 0)
|
|
151
|
+
continue;
|
|
152
|
+
const content = await readTextFile(join(rootPath, candidates[0]));
|
|
153
|
+
if (!content)
|
|
154
|
+
continue;
|
|
155
|
+
const lines = content.split("\n").slice(0, 40);
|
|
156
|
+
const snippet = lines.join("\n");
|
|
157
|
+
const classMatch = snippet.match(/((?:export\s+)?(?:abstract\s+)?class\s+\w+[^{]*\{[^}]*(?:\n[^}]*){0,8})/);
|
|
158
|
+
const fnMatch = snippet.match(/((?:export\s+)?(?:async\s+)?function\s+\w+[^{]*\{[^}]*(?:\n[^}]*){0,5})/);
|
|
159
|
+
const code = classMatch?.[1] || fnMatch?.[1] || lines.slice(0, 15).join("\n");
|
|
160
|
+
if (code.trim().length > 30) {
|
|
161
|
+
examples.push({
|
|
162
|
+
file: candidates[0],
|
|
163
|
+
label: cat.label,
|
|
164
|
+
code: code.trim().slice(0, 500),
|
|
165
|
+
category: cat.category,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (examples.length >= 6)
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
if (depNames.includes("@cubos/inject") || depNames.includes("tsyringe") || depNames.includes("inversify")) {
|
|
172
|
+
const cfg = getScanConfig();
|
|
173
|
+
const diFiles = await readFilesMatching(rootPath, allFiles, {
|
|
174
|
+
extensions: [".ts"],
|
|
175
|
+
maxFiles: cfg.maxFiles,
|
|
176
|
+
maxFileSize: cfg.maxFileSize,
|
|
177
|
+
pathPatterns: [/(services?|controllers?|use-?cases?)\//],
|
|
178
|
+
});
|
|
179
|
+
for (const { file, content } of diFiles) {
|
|
180
|
+
const useMatch = content.match(/.*use\(\w+\).*/);
|
|
181
|
+
const injectMatch = content.match(/.*@Inject.*|.*@injectable.*/i);
|
|
182
|
+
const line = useMatch?.[0] || injectMatch?.[0];
|
|
183
|
+
if (line) {
|
|
184
|
+
examples.push({ file, label: "DI usage", code: line.trim(), category: "di" });
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return examples;
|
|
190
|
+
}
|
|
191
|
+
// ─── Workflows ────────────────────────────────────────────
|
|
192
|
+
export async function detectWorkflows(rootPath, allFiles, deps, frameworks) {
|
|
193
|
+
const workflows = [];
|
|
194
|
+
const depNames = deps.map((d) => d.name);
|
|
195
|
+
if (allFiles.some((f) => f.endsWith(".sdkgen"))) {
|
|
196
|
+
workflows.push("SDKGEN: define .sdkgen schema → `pnpm run sdkgen` → implement controllers");
|
|
197
|
+
}
|
|
198
|
+
if (depNames.includes("@graphql-codegen/cli") || allFiles.some((f) => f.includes("codegen"))) {
|
|
199
|
+
workflows.push("GraphQL Codegen: .graphql schema → generated types/resolvers");
|
|
200
|
+
}
|
|
201
|
+
if (allFiles.some((f) => f.endsWith("schema.prisma"))) {
|
|
202
|
+
workflows.push("Prisma: edit schema.prisma → `prisma generate` → `prisma migrate dev`");
|
|
203
|
+
}
|
|
204
|
+
if (depNames.includes("drizzle-kit")) {
|
|
205
|
+
workflows.push("Drizzle: edit schema → `drizzle-kit generate` → `drizzle-kit migrate`");
|
|
206
|
+
}
|
|
207
|
+
if (allFiles.some((f) => f.includes("knexfile"))) {
|
|
208
|
+
workflows.push("Knex migrations: `knex migrate:make <name>` → edit → `knex migrate:latest`");
|
|
209
|
+
}
|
|
210
|
+
if (allFiles.some((f) => f.match(/openapi|swagger/i) && f.match(/\.(json|yaml|yml)$/))) {
|
|
211
|
+
workflows.push("OpenAPI/Swagger spec → generated client/types");
|
|
212
|
+
}
|
|
213
|
+
if (depNames.includes("turbo"))
|
|
214
|
+
workflows.push("Turborepo: `turbo run <task>` across packages");
|
|
215
|
+
if (depNames.includes("lerna"))
|
|
216
|
+
workflows.push("Lerna: `lerna run <task>` across packages");
|
|
217
|
+
if (allFiles.includes("nx.json"))
|
|
218
|
+
workflows.push("Nx: `nx run <project>:<task>`");
|
|
219
|
+
if (allFiles.some((f) => f.includes(".github/workflows/")))
|
|
220
|
+
workflows.push("GitHub Actions CI/CD");
|
|
221
|
+
if (allFiles.some((f) => f.includes(".gitlab-ci")))
|
|
222
|
+
workflows.push("GitLab CI/CD");
|
|
223
|
+
if (allFiles.includes("Jenkinsfile"))
|
|
224
|
+
workflows.push("Jenkins CI/CD");
|
|
225
|
+
if (depNames.includes("husky"))
|
|
226
|
+
workflows.push("Husky: pre-commit hooks for linting/formatting");
|
|
227
|
+
if (depNames.includes("commitizen") || depNames.includes("git-cz"))
|
|
228
|
+
workflows.push("Commitizen: standardized commit messages (`git cz`)");
|
|
229
|
+
if (allFiles.includes("Dockerfile"))
|
|
230
|
+
workflows.push("Docker: containerized deployment");
|
|
231
|
+
if (allFiles.some((f) => f.match(/(docker-)?compose\.(yml|yaml)$/)))
|
|
232
|
+
workflows.push("Docker Compose: multi-service local dev");
|
|
233
|
+
if (depNames.includes("electron") || depNames.includes("electron-builder")) {
|
|
234
|
+
workflows.push("Electron: build → bundle → `electron-builder` dist");
|
|
235
|
+
}
|
|
236
|
+
return workflows;
|
|
237
|
+
}
|
|
238
|
+
// ─── Code Conventions ─────────────────────────────────────
|
|
239
|
+
export async function detectCodeConventions(rootPath, allFiles) {
|
|
240
|
+
const conventions = [];
|
|
241
|
+
const tsconfig = await readTextFile(join(rootPath, "tsconfig.json"));
|
|
242
|
+
if (tsconfig) {
|
|
243
|
+
if (tsconfig.includes('"strict": true') || tsconfig.includes('"strict":true')) {
|
|
244
|
+
conventions.push("TypeScript strict mode enabled");
|
|
245
|
+
}
|
|
246
|
+
if (tsconfig.includes('"noImplicitAny"')) {
|
|
247
|
+
conventions.push("No implicit any types");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const eslintFiles = allFiles.filter((f) => f.match(/eslint/i) && !f.includes("node_modules"));
|
|
251
|
+
if (eslintFiles.length > 0) {
|
|
252
|
+
const content = await readTextFile(join(rootPath, eslintFiles[0]));
|
|
253
|
+
if (content) {
|
|
254
|
+
if (content.includes("no-any") || content.includes("@typescript-eslint/no-explicit-any")) {
|
|
255
|
+
conventions.push("No `any` type allowed (ESLint enforced)");
|
|
256
|
+
}
|
|
257
|
+
if (content.includes("no-console")) {
|
|
258
|
+
conventions.push("No console.log (use logger instead)");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const cfg = getScanConfig();
|
|
263
|
+
const repoFiles = await readFilesMatching(rootPath, allFiles, {
|
|
264
|
+
extensions: [".ts", ".js"],
|
|
265
|
+
maxFiles: cfg.maxFiles,
|
|
266
|
+
maxFileSize: cfg.maxFileSize,
|
|
267
|
+
pathPatterns: [/repositor/i],
|
|
268
|
+
});
|
|
269
|
+
for (const { content } of repoFiles) {
|
|
270
|
+
if (content.includes("isDeleted") || content.includes("is_deleted") || content.includes("deletedAt")) {
|
|
271
|
+
conventions.push("Soft delete pattern (filter `isDeleted`/`deletedAt`)");
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (const { content } of repoFiles) {
|
|
276
|
+
if (content.match(/class\s+\w+\s+extends\s+Base(Repository|Repo)/)) {
|
|
277
|
+
conventions.push("BaseRepository pattern for data access");
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return conventions;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=patterns-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns-code.js","sourceRoot":"","sources":["../../src/scanner/patterns-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/scanner/patterns.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/scanner/patterns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQlE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,IAAI,EAAE,eAAe,EAAE,EACvB,UAAU,EAAE,aAAa,EAAE,GAC1B,OAAO,CAAC,YAAY,CAAC,CAoBvB"}
|
package/dist/scanner/patterns.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
// ─── Code Patterns Scanner — Orchestrator + Import/Naming/IDE
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { readTextFile, readFilesMatching } from "../utils/files.js";
|
|
3
4
|
import { getScanConfig } from "./config.js";
|
|
5
|
+
import { detectErrorHandling, detectTestPatterns, detectArchitecturalRules, extractCodeExamples, detectWorkflows, detectCodeConventions, } from "./patterns-code.js";
|
|
4
6
|
export async function scanCodePatterns(rootPath, allFiles, deps, frameworks) {
|
|
5
7
|
const [di, imports, naming, workflows, codeConventions, ideSkills, ideCommands, errorHandling, testPatterns, architecturalRules, codeExamples] = await Promise.all([
|
|
6
8
|
detectDI(rootPath, allFiles, deps),
|
|
@@ -120,7 +122,6 @@ async function detectNaming(rootPath, allFiles) {
|
|
|
120
122
|
fileNaming = "PascalCase (MyComponent.ts)";
|
|
121
123
|
else if (camelCount === maxCount && camelCount > kebabCount + pascalCount)
|
|
122
124
|
fileNaming = "camelCase (myComponent.ts)";
|
|
123
|
-
// DB column naming — read actual schema/migrations
|
|
124
125
|
let dbColumns = "unknown";
|
|
125
126
|
const prismaFile = allFiles.find((f) => f.endsWith("schema.prisma"));
|
|
126
127
|
if (prismaFile) {
|
|
@@ -158,293 +159,6 @@ async function detectNaming(rootPath, allFiles) {
|
|
|
158
159
|
}
|
|
159
160
|
return { files: fileNaming, variables: "camelCase", dbColumns };
|
|
160
161
|
}
|
|
161
|
-
// ─── Error Handling ───────────────────────────────────────
|
|
162
|
-
async function detectErrorHandling(rootPath, allFiles) {
|
|
163
|
-
const patterns = [];
|
|
164
|
-
const cfg = getScanConfig();
|
|
165
|
-
const sourceFiles = await readFilesMatching(rootPath, allFiles, {
|
|
166
|
-
extensions: [".ts", ".js"],
|
|
167
|
-
maxFiles: cfg.maxFiles,
|
|
168
|
-
maxFileSize: cfg.maxFileSize,
|
|
169
|
-
});
|
|
170
|
-
for (const { file, content } of sourceFiles) {
|
|
171
|
-
const regex = /class\s+(\w+)\s+extends\s+(Error|BaseError|DomainError|AppError|HttpException|CustomError|BusinessError|NotFound|BadRequest|Unauthorized|Forbidden)\b/g;
|
|
172
|
-
let match;
|
|
173
|
-
while ((match = regex.exec(content)) !== null) {
|
|
174
|
-
patterns.push({ className: match[1], extendsFrom: match[2], file });
|
|
175
|
-
}
|
|
176
|
-
if (patterns.length >= 30)
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
// Dedupe by className
|
|
180
|
-
const seen = new Set();
|
|
181
|
-
return patterns.filter((p) => {
|
|
182
|
-
if (seen.has(p.className))
|
|
183
|
-
return false;
|
|
184
|
-
seen.add(p.className);
|
|
185
|
-
return true;
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
// ─── Test Patterns ────────────────────────────────────────
|
|
189
|
-
async function detectTestPatterns(rootPath, allFiles, deps, frameworks) {
|
|
190
|
-
const depNames = deps.map((d) => d.name);
|
|
191
|
-
const fwNames = frameworks.map((f) => f.name);
|
|
192
|
-
let framework = "";
|
|
193
|
-
if (depNames.includes("vitest") || fwNames.includes("Vitest"))
|
|
194
|
-
framework = "Vitest";
|
|
195
|
-
else if (depNames.includes("jest") || depNames.includes("ts-jest") || fwNames.includes("Jest"))
|
|
196
|
-
framework = "Jest";
|
|
197
|
-
else if (depNames.includes("mocha"))
|
|
198
|
-
framework = "Mocha";
|
|
199
|
-
if (!framework)
|
|
200
|
-
return null;
|
|
201
|
-
// Coverage threshold
|
|
202
|
-
let coverageThreshold = null;
|
|
203
|
-
const configFiles = allFiles.filter((f) => f.match(/(jest|vitest)\..*config\.(ts|js|mjs|cjs|json)$/));
|
|
204
|
-
for (const cf of configFiles.slice(0, 3)) {
|
|
205
|
-
const content = await readTextFile(join(rootPath, cf));
|
|
206
|
-
if (!content)
|
|
207
|
-
continue;
|
|
208
|
-
const thresholdMatch = content.match(/(?:branches|lines|functions|statements)\s*:\s*(\d+)/);
|
|
209
|
-
if (thresholdMatch) {
|
|
210
|
-
coverageThreshold = parseInt(thresholdMatch[1], 10);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// File patterns
|
|
215
|
-
const testFiles = allFiles.filter((f) => f.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/));
|
|
216
|
-
const specCount = testFiles.filter((f) => f.includes(".spec.")).length;
|
|
217
|
-
const testCount = testFiles.filter((f) => f.includes(".test.")).length;
|
|
218
|
-
const unitFilePattern = specCount > testCount ? "*.spec.ts" : "*.test.ts";
|
|
219
|
-
let integrationFilePattern = null;
|
|
220
|
-
if (allFiles.some((f) => f.includes("__tests__/")))
|
|
221
|
-
integrationFilePattern = "__tests__/**/*.test.ts";
|
|
222
|
-
else if (allFiles.some((f) => f.includes("e2e/")))
|
|
223
|
-
integrationFilePattern = "e2e/**/*.test.ts";
|
|
224
|
-
let testDir = "src/";
|
|
225
|
-
if (allFiles.some((f) => f.includes("__tests__/")))
|
|
226
|
-
testDir = "__tests__/";
|
|
227
|
-
else if (allFiles.some((f) => f.match(/^tests?\//)))
|
|
228
|
-
testDir = "tests/";
|
|
229
|
-
// Assertion style
|
|
230
|
-
let assertionStyle = null;
|
|
231
|
-
if (testFiles.length > 0) {
|
|
232
|
-
const content = await readTextFile(join(rootPath, testFiles[0]));
|
|
233
|
-
if (content) {
|
|
234
|
-
if (content.includes("expect("))
|
|
235
|
-
assertionStyle = "expect() (Jest/Vitest)";
|
|
236
|
-
else if (content.includes("assert."))
|
|
237
|
-
assertionStyle = "assert (Node.js/Chai)";
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return { framework, coverageThreshold, unitFilePattern, integrationFilePattern, testDir, assertionStyle };
|
|
241
|
-
}
|
|
242
|
-
// ─── Architectural Rules ──────────────────────────────────
|
|
243
|
-
async function detectArchitecturalRules(rootPath, allFiles) {
|
|
244
|
-
const rules = [];
|
|
245
|
-
// 1. dependency-cruiser config
|
|
246
|
-
const dcFile = allFiles.find((f) => f.match(/\.dependency-cruiser\.(cjs|js|json)$/));
|
|
247
|
-
if (dcFile) {
|
|
248
|
-
const content = await readTextFile(join(rootPath, dcFile));
|
|
249
|
-
if (content) {
|
|
250
|
-
const nameRegex = /name\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
251
|
-
let match;
|
|
252
|
-
while ((match = nameRegex.exec(content)) !== null) {
|
|
253
|
-
rules.push(`Dependency rule: ${match[1]}`);
|
|
254
|
-
if (rules.length >= 10)
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// 2. ESLint boundary rules
|
|
260
|
-
const eslintFiles = allFiles.filter((f) => f.match(/eslint/i) && !f.includes("node_modules"));
|
|
261
|
-
for (const ef of eslintFiles.slice(0, 2)) {
|
|
262
|
-
const content = await readTextFile(join(rootPath, ef));
|
|
263
|
-
if (!content)
|
|
264
|
-
continue;
|
|
265
|
-
if (content.includes("import/no-restricted-paths") || content.includes("boundaries/element-types")) {
|
|
266
|
-
rules.push("ESLint enforced import boundaries between layers");
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
// 3. Infer from structure — check if domain doesn't import from infra
|
|
270
|
-
const hasDomain = allFiles.some((f) => f.match(/src\/(domain|core)\//));
|
|
271
|
-
const hasInfra = allFiles.some((f) => f.match(/src\/(infra|infrastructure)\//));
|
|
272
|
-
const hasApp = allFiles.some((f) => f.match(/src\/(application|use-?cases)\//));
|
|
273
|
-
if (hasDomain && hasInfra) {
|
|
274
|
-
const cfg = getScanConfig();
|
|
275
|
-
const domainFiles = await readFilesMatching(rootPath, allFiles, {
|
|
276
|
-
extensions: [".ts", ".js"],
|
|
277
|
-
maxFiles: cfg.maxFiles,
|
|
278
|
-
maxFileSize: cfg.maxFileSize,
|
|
279
|
-
pathPatterns: [/src\/(domain|core)\//],
|
|
280
|
-
});
|
|
281
|
-
let domainImportsInfra = false;
|
|
282
|
-
for (const { content } of domainFiles) {
|
|
283
|
-
if (content.match(/from\s+['"].*\/(infra|infrastructure)\//)) {
|
|
284
|
-
domainImportsInfra = true;
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (!domainImportsInfra) {
|
|
289
|
-
rules.push("Domain/Core layer does NOT import from Infrastructure (Clean Architecture)");
|
|
290
|
-
}
|
|
291
|
-
if (hasApp) {
|
|
292
|
-
rules.push("Layers: Domain → Application → Infrastructure");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return rules;
|
|
296
|
-
}
|
|
297
|
-
// ─── Code Examples ────────────────────────────────────────
|
|
298
|
-
async function extractCodeExamples(rootPath, allFiles, deps) {
|
|
299
|
-
const examples = [];
|
|
300
|
-
const depNames = deps.map((d) => d.name);
|
|
301
|
-
const categories = [
|
|
302
|
-
{ category: "service", patterns: [/services?\//i], label: "Service pattern" },
|
|
303
|
-
{ category: "controller", patterns: [/controllers?\//i], label: "Controller pattern" },
|
|
304
|
-
{ category: "repository", patterns: [/repositor/i], label: "Repository pattern" },
|
|
305
|
-
{ category: "middleware", patterns: [/middleware/i], label: "Middleware pattern" },
|
|
306
|
-
{ category: "error-handling", patterns: [/(errors|exceptions)\//i], label: "Error handling" },
|
|
307
|
-
];
|
|
308
|
-
for (const cat of categories) {
|
|
309
|
-
const candidates = allFiles.filter((f) => f.match(/\.(ts|js)$/) &&
|
|
310
|
-
cat.patterns.some((p) => p.test(f)) &&
|
|
311
|
-
!f.includes(".test.") && !f.includes(".spec.") &&
|
|
312
|
-
!f.includes("index.") && !f.includes("generated/"));
|
|
313
|
-
if (candidates.length === 0)
|
|
314
|
-
continue;
|
|
315
|
-
const content = await readTextFile(join(rootPath, candidates[0]));
|
|
316
|
-
if (!content)
|
|
317
|
-
continue;
|
|
318
|
-
const lines = content.split("\n").slice(0, 40);
|
|
319
|
-
// Extract meaningful code: class definition or function
|
|
320
|
-
const snippet = lines.join("\n");
|
|
321
|
-
const classMatch = snippet.match(/((?:export\s+)?(?:abstract\s+)?class\s+\w+[^{]*\{[^}]*(?:\n[^}]*){0,8})/);
|
|
322
|
-
const fnMatch = snippet.match(/((?:export\s+)?(?:async\s+)?function\s+\w+[^{]*\{[^}]*(?:\n[^}]*){0,5})/);
|
|
323
|
-
const code = classMatch?.[1] || fnMatch?.[1] || lines.slice(0, 15).join("\n");
|
|
324
|
-
if (code.trim().length > 30) {
|
|
325
|
-
examples.push({
|
|
326
|
-
file: candidates[0],
|
|
327
|
-
label: cat.label,
|
|
328
|
-
code: code.trim().slice(0, 500),
|
|
329
|
-
category: cat.category,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
if (examples.length >= 6)
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
// DI example
|
|
336
|
-
if (depNames.includes("@cubos/inject") || depNames.includes("tsyringe") || depNames.includes("inversify")) {
|
|
337
|
-
const cfg = getScanConfig();
|
|
338
|
-
const diFiles = await readFilesMatching(rootPath, allFiles, {
|
|
339
|
-
extensions: [".ts"],
|
|
340
|
-
maxFiles: cfg.maxFiles,
|
|
341
|
-
maxFileSize: cfg.maxFileSize,
|
|
342
|
-
pathPatterns: [/(services?|controllers?|use-?cases?)\//],
|
|
343
|
-
});
|
|
344
|
-
for (const { file, content } of diFiles) {
|
|
345
|
-
const useMatch = content.match(/.*use\(\w+\).*/);
|
|
346
|
-
const injectMatch = content.match(/.*@Inject.*|.*@injectable.*/i);
|
|
347
|
-
const line = useMatch?.[0] || injectMatch?.[0];
|
|
348
|
-
if (line) {
|
|
349
|
-
examples.push({ file, label: "DI usage", code: line.trim(), category: "di" });
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return examples;
|
|
355
|
-
}
|
|
356
|
-
// ─── Workflows ────────────────────────────────────────────
|
|
357
|
-
async function detectWorkflows(rootPath, allFiles, deps, frameworks) {
|
|
358
|
-
const workflows = [];
|
|
359
|
-
const depNames = deps.map((d) => d.name);
|
|
360
|
-
if (allFiles.some((f) => f.endsWith(".sdkgen"))) {
|
|
361
|
-
workflows.push("SDKGEN: define .sdkgen schema → `pnpm run sdkgen` → implement controllers");
|
|
362
|
-
}
|
|
363
|
-
if (depNames.includes("@graphql-codegen/cli") || allFiles.some((f) => f.includes("codegen"))) {
|
|
364
|
-
workflows.push("GraphQL Codegen: .graphql schema → generated types/resolvers");
|
|
365
|
-
}
|
|
366
|
-
if (allFiles.some((f) => f.endsWith("schema.prisma"))) {
|
|
367
|
-
workflows.push("Prisma: edit schema.prisma → `prisma generate` → `prisma migrate dev`");
|
|
368
|
-
}
|
|
369
|
-
if (depNames.includes("drizzle-kit")) {
|
|
370
|
-
workflows.push("Drizzle: edit schema → `drizzle-kit generate` → `drizzle-kit migrate`");
|
|
371
|
-
}
|
|
372
|
-
if (allFiles.some((f) => f.includes("knexfile"))) {
|
|
373
|
-
workflows.push("Knex migrations: `knex migrate:make <name>` → edit → `knex migrate:latest`");
|
|
374
|
-
}
|
|
375
|
-
if (allFiles.some((f) => f.match(/openapi|swagger/i) && f.match(/\.(json|yaml|yml)$/))) {
|
|
376
|
-
workflows.push("OpenAPI/Swagger spec → generated client/types");
|
|
377
|
-
}
|
|
378
|
-
if (depNames.includes("turbo"))
|
|
379
|
-
workflows.push("Turborepo: `turbo run <task>` across packages");
|
|
380
|
-
if (depNames.includes("lerna"))
|
|
381
|
-
workflows.push("Lerna: `lerna run <task>` across packages");
|
|
382
|
-
if (allFiles.includes("nx.json"))
|
|
383
|
-
workflows.push("Nx: `nx run <project>:<task>`");
|
|
384
|
-
if (allFiles.some((f) => f.includes(".github/workflows/")))
|
|
385
|
-
workflows.push("GitHub Actions CI/CD");
|
|
386
|
-
if (allFiles.some((f) => f.includes(".gitlab-ci")))
|
|
387
|
-
workflows.push("GitLab CI/CD");
|
|
388
|
-
if (allFiles.includes("Jenkinsfile"))
|
|
389
|
-
workflows.push("Jenkins CI/CD");
|
|
390
|
-
if (depNames.includes("husky"))
|
|
391
|
-
workflows.push("Husky: pre-commit hooks for linting/formatting");
|
|
392
|
-
if (depNames.includes("commitizen") || depNames.includes("git-cz"))
|
|
393
|
-
workflows.push("Commitizen: standardized commit messages (`git cz`)");
|
|
394
|
-
if (allFiles.includes("Dockerfile"))
|
|
395
|
-
workflows.push("Docker: containerized deployment");
|
|
396
|
-
if (allFiles.some((f) => f.match(/(docker-)?compose\.(yml|yaml)$/)))
|
|
397
|
-
workflows.push("Docker Compose: multi-service local dev");
|
|
398
|
-
if (depNames.includes("electron") || depNames.includes("electron-builder")) {
|
|
399
|
-
workflows.push("Electron: build → bundle → `electron-builder` dist");
|
|
400
|
-
}
|
|
401
|
-
return workflows;
|
|
402
|
-
}
|
|
403
|
-
// ─── Code Conventions ─────────────────────────────────────
|
|
404
|
-
async function detectCodeConventions(rootPath, allFiles) {
|
|
405
|
-
const conventions = [];
|
|
406
|
-
const tsconfig = await readTextFile(join(rootPath, "tsconfig.json"));
|
|
407
|
-
if (tsconfig) {
|
|
408
|
-
if (tsconfig.includes('"strict": true') || tsconfig.includes('"strict":true')) {
|
|
409
|
-
conventions.push("TypeScript strict mode enabled");
|
|
410
|
-
}
|
|
411
|
-
if (tsconfig.includes('"noImplicitAny"')) {
|
|
412
|
-
conventions.push("No implicit any types");
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const eslintFiles = allFiles.filter((f) => f.match(/eslint/i) && !f.includes("node_modules"));
|
|
416
|
-
if (eslintFiles.length > 0) {
|
|
417
|
-
const content = await readTextFile(join(rootPath, eslintFiles[0]));
|
|
418
|
-
if (content) {
|
|
419
|
-
if (content.includes("no-any") || content.includes("@typescript-eslint/no-explicit-any")) {
|
|
420
|
-
conventions.push("No `any` type allowed (ESLint enforced)");
|
|
421
|
-
}
|
|
422
|
-
if (content.includes("no-console")) {
|
|
423
|
-
conventions.push("No console.log (use logger instead)");
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const cfg = getScanConfig();
|
|
428
|
-
const repoFiles = await readFilesMatching(rootPath, allFiles, {
|
|
429
|
-
extensions: [".ts", ".js"],
|
|
430
|
-
maxFiles: cfg.maxFiles,
|
|
431
|
-
maxFileSize: cfg.maxFileSize,
|
|
432
|
-
pathPatterns: [/repositor/i],
|
|
433
|
-
});
|
|
434
|
-
for (const { content } of repoFiles) {
|
|
435
|
-
if (content.includes("isDeleted") || content.includes("is_deleted") || content.includes("deletedAt")) {
|
|
436
|
-
conventions.push("Soft delete pattern (filter `isDeleted`/`deletedAt`)");
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
for (const { content } of repoFiles) {
|
|
441
|
-
if (content.match(/class\s+\w+\s+extends\s+Base(Repository|Repo)/)) {
|
|
442
|
-
conventions.push("BaseRepository pattern for data access");
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return conventions;
|
|
447
|
-
}
|
|
448
162
|
// ─── IDE Skills ───────────────────────────────────────────
|
|
449
163
|
async function scanIDESkills(rootPath, allFiles) {
|
|
450
164
|
const skills = [];
|