@barissozen/csns 0.6.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/.env.example +8 -0
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/dist/agent/agent-runner.d.ts +48 -0
- package/dist/agent/agent-runner.d.ts.map +1 -0
- package/dist/agent/agent-runner.js +180 -0
- package/dist/agent/agent-runner.js.map +1 -0
- package/dist/agent/architect.d.ts +34 -0
- package/dist/agent/architect.d.ts.map +1 -0
- package/dist/agent/architect.js +156 -0
- package/dist/agent/architect.js.map +1 -0
- package/dist/agent/codebase-reader.d.ts +35 -0
- package/dist/agent/codebase-reader.d.ts.map +1 -0
- package/dist/agent/codebase-reader.js +305 -0
- package/dist/agent/codebase-reader.js.map +1 -0
- package/dist/agent/context-builder.d.ts +39 -0
- package/dist/agent/context-builder.d.ts.map +1 -0
- package/dist/agent/context-builder.js +163 -0
- package/dist/agent/context-builder.js.map +1 -0
- package/dist/agent/index.d.ts +14 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +12 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/output-parser.d.ts +27 -0
- package/dist/agent/output-parser.d.ts.map +1 -0
- package/dist/agent/output-parser.js +154 -0
- package/dist/agent/output-parser.js.map +1 -0
- package/dist/agent/process-spawner.d.ts +56 -0
- package/dist/agent/process-spawner.d.ts.map +1 -0
- package/dist/agent/process-spawner.js +153 -0
- package/dist/agent/process-spawner.js.map +1 -0
- package/dist/agent/tracer/index.d.ts +8 -0
- package/dist/agent/tracer/index.d.ts.map +1 -0
- package/dist/agent/tracer/index.js +6 -0
- package/dist/agent/tracer/index.js.map +1 -0
- package/dist/agent/tracer/reverse-engineer.d.ts +113 -0
- package/dist/agent/tracer/reverse-engineer.d.ts.map +1 -0
- package/dist/agent/tracer/reverse-engineer.js +695 -0
- package/dist/agent/tracer/reverse-engineer.js.map +1 -0
- package/dist/agent/tracer/runtime-tracer.d.ts +61 -0
- package/dist/agent/tracer/runtime-tracer.d.ts.map +1 -0
- package/dist/agent/tracer/runtime-tracer.js +484 -0
- package/dist/agent/tracer/runtime-tracer.js.map +1 -0
- package/dist/agent/tracer/semantic-analyzer.d.ts +24 -0
- package/dist/agent/tracer/semantic-analyzer.d.ts.map +1 -0
- package/dist/agent/tracer/semantic-analyzer.js +132 -0
- package/dist/agent/tracer/semantic-analyzer.js.map +1 -0
- package/dist/agent/tracer/static-analyzer.d.ts +44 -0
- package/dist/agent/tracer/static-analyzer.d.ts.map +1 -0
- package/dist/agent/tracer/static-analyzer.js +453 -0
- package/dist/agent/tracer/static-analyzer.js.map +1 -0
- package/dist/agent/tracer/tracer-agent.d.ts +61 -0
- package/dist/agent/tracer/tracer-agent.d.ts.map +1 -0
- package/dist/agent/tracer/tracer-agent.js +252 -0
- package/dist/agent/tracer/tracer-agent.js.map +1 -0
- package/dist/bin/csns.d.ts +24 -0
- package/dist/bin/csns.d.ts.map +1 -0
- package/dist/bin/csns.js +389 -0
- package/dist/bin/csns.js.map +1 -0
- package/dist/bin/pc.d.ts +13 -0
- package/dist/bin/pc.d.ts.map +1 -0
- package/dist/bin/pc.js +212 -0
- package/dist/bin/pc.js.map +1 -0
- package/dist/brief/brief-collector.d.ts +42 -0
- package/dist/brief/brief-collector.d.ts.map +1 -0
- package/dist/brief/brief-collector.js +228 -0
- package/dist/brief/brief-collector.js.map +1 -0
- package/dist/brief/index.d.ts +3 -0
- package/dist/brief/index.d.ts.map +1 -0
- package/dist/brief/index.js +3 -0
- package/dist/brief/index.js.map +1 -0
- package/dist/brief/smart-brief.d.ts +52 -0
- package/dist/brief/smart-brief.d.ts.map +1 -0
- package/dist/brief/smart-brief.js +440 -0
- package/dist/brief/smart-brief.js.map +1 -0
- package/dist/calculator/calculator.d.ts +7 -0
- package/dist/calculator/calculator.d.ts.map +1 -0
- package/dist/calculator/calculator.js +18 -0
- package/dist/calculator/calculator.js.map +1 -0
- package/dist/calculator/index.d.ts +2 -0
- package/dist/calculator/index.d.ts.map +1 -0
- package/dist/calculator/index.js +2 -0
- package/dist/calculator/index.js.map +1 -0
- package/dist/i18n/en.d.ts +6 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +136 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/index.d.ts +31 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +44 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/tr.d.ts +6 -0
- package/dist/i18n/tr.d.ts.map +1 -0
- package/dist/i18n/tr.js +136 -0
- package/dist/i18n/tr.js.map +1 -0
- package/dist/i18n/types.d.ts +86 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +9 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic-provider.d.ts +18 -0
- package/dist/llm/anthropic-provider.d.ts.map +1 -0
- package/dist/llm/anthropic-provider.js +55 -0
- package/dist/llm/anthropic-provider.js.map +1 -0
- package/dist/llm/factory.d.ts +21 -0
- package/dist/llm/factory.d.ts.map +1 -0
- package/dist/llm/factory.js +59 -0
- package/dist/llm/factory.js.map +1 -0
- package/dist/llm/index.d.ts +7 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +5 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/ollama-provider.d.ts +20 -0
- package/dist/llm/ollama-provider.d.ts.map +1 -0
- package/dist/llm/ollama-provider.js +62 -0
- package/dist/llm/ollama-provider.js.map +1 -0
- package/dist/llm/openai-provider.d.ts +20 -0
- package/dist/llm/openai-provider.d.ts.map +1 -0
- package/dist/llm/openai-provider.js +65 -0
- package/dist/llm/openai-provider.js.map +1 -0
- package/dist/llm/resolve.d.ts +10 -0
- package/dist/llm/resolve.d.ts.map +1 -0
- package/dist/llm/resolve.js +21 -0
- package/dist/llm/resolve.js.map +1 -0
- package/dist/llm/types.d.ts +45 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +11 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +2 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memory-layer.d.ts +54 -0
- package/dist/memory/memory-layer.d.ts.map +1 -0
- package/dist/memory/memory-layer.js +297 -0
- package/dist/memory/memory-layer.js.map +1 -0
- package/dist/orchestrator/dependency-graph.d.ts +39 -0
- package/dist/orchestrator/dependency-graph.d.ts.map +1 -0
- package/dist/orchestrator/dependency-graph.js +134 -0
- package/dist/orchestrator/dependency-graph.js.map +1 -0
- package/dist/orchestrator/escalator.d.ts +42 -0
- package/dist/orchestrator/escalator.d.ts.map +1 -0
- package/dist/orchestrator/escalator.js +163 -0
- package/dist/orchestrator/escalator.js.map +1 -0
- package/dist/orchestrator/evaluator.d.ts +34 -0
- package/dist/orchestrator/evaluator.d.ts.map +1 -0
- package/dist/orchestrator/evaluator.js +335 -0
- package/dist/orchestrator/evaluator.js.map +1 -0
- package/dist/orchestrator/index.d.ts +9 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +9 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/integration-evaluator.d.ts +59 -0
- package/dist/orchestrator/integration-evaluator.d.ts.map +1 -0
- package/dist/orchestrator/integration-evaluator.js +405 -0
- package/dist/orchestrator/integration-evaluator.js.map +1 -0
- package/dist/orchestrator/milestone-manager.d.ts +28 -0
- package/dist/orchestrator/milestone-manager.d.ts.map +1 -0
- package/dist/orchestrator/milestone-manager.js +208 -0
- package/dist/orchestrator/milestone-manager.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts +35 -0
- package/dist/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator.js +338 -0
- package/dist/orchestrator/orchestrator.js.map +1 -0
- package/dist/orchestrator/planner.d.ts +15 -0
- package/dist/orchestrator/planner.d.ts.map +1 -0
- package/dist/orchestrator/planner.js +87 -0
- package/dist/orchestrator/planner.js.map +1 -0
- package/dist/orchestrator/recovery.d.ts +45 -0
- package/dist/orchestrator/recovery.d.ts.map +1 -0
- package/dist/orchestrator/recovery.js +98 -0
- package/dist/orchestrator/recovery.js.map +1 -0
- package/dist/run-calculator-test.d.ts +11 -0
- package/dist/run-calculator-test.d.ts.map +1 -0
- package/dist/run-calculator-test.js +212 -0
- package/dist/run-calculator-test.js.map +1 -0
- package/dist/run-real-task.d.ts +14 -0
- package/dist/run-real-task.d.ts.map +1 -0
- package/dist/run-real-task.js +185 -0
- package/dist/run-real-task.js.map +1 -0
- package/dist/run-todo-test.d.ts +6 -0
- package/dist/run-todo-test.d.ts.map +1 -0
- package/dist/run-todo-test.js +149 -0
- package/dist/run-todo-test.js.map +1 -0
- package/dist/todo/index.d.ts +2 -0
- package/dist/todo/index.d.ts.map +1 -0
- package/dist/todo/index.js +2 -0
- package/dist/todo/index.js.map +1 -0
- package/dist/todo/server.d.ts +2 -0
- package/dist/todo/server.d.ts.map +1 -0
- package/dist/todo/server.js +32 -0
- package/dist/todo/server.js.map +1 -0
- package/dist/types/index.d.ts +412 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reverse Engineer — Codebase Audit via Architecture Recovery
|
|
3
|
+
*
|
|
4
|
+
* Mevcut bir kodu tersine mühendislik ile analiz eder:
|
|
5
|
+
* 1. Katman tespiti: hangi dosya controller, service, repo, model, util?
|
|
6
|
+
* 2. Data flow zinciri: request → middleware → controller → service → repo → response
|
|
7
|
+
* 3. Mimari tutarlılık: ARCHITECTURE.md'deki kararlar kodda uygulanmış mı?
|
|
8
|
+
* 4. Karar arkeolojisi: DECISIONS.md'deki kararlar gerçekten var mı?
|
|
9
|
+
* 5. Pattern tespiti: hangi design pattern'ler kullanılmış, tutarlı mı?
|
|
10
|
+
*
|
|
11
|
+
* Tracer Agent'ın static + runtime verisini alıp üzerine semantic katman ekler.
|
|
12
|
+
*/
|
|
13
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
14
|
+
import { join, relative, extname } from 'node:path';
|
|
15
|
+
// ═══════════════════════════════════════════════════════════
|
|
16
|
+
// Regex Pattern Libraries
|
|
17
|
+
// ═══════════════════════════════════════════════════════════
|
|
18
|
+
/** Layer classification signals from file path */
|
|
19
|
+
const PATH_SIGNALS = [
|
|
20
|
+
{ pattern: /\broute[sr]?\b/i, layer: 'route', weight: 8 },
|
|
21
|
+
{ pattern: /\bcontroller[s]?\b/i, layer: 'controller', weight: 8 },
|
|
22
|
+
{ pattern: /\bmiddleware[s]?\b/i, layer: 'middleware', weight: 9 },
|
|
23
|
+
{ pattern: /\bservice[s]?\b/i, layer: 'service', weight: 8 },
|
|
24
|
+
{ pattern: /\brepo(?:sitor(?:y|ies))?\b/i, layer: 'repository', weight: 8 },
|
|
25
|
+
{ pattern: /\bdal\b/i, layer: 'repository', weight: 7 },
|
|
26
|
+
{ pattern: /\bmodel[s]?\b/i, layer: 'model', weight: 7 },
|
|
27
|
+
{ pattern: /\bentit(?:y|ies)\b/i, layer: 'model', weight: 7 },
|
|
28
|
+
{ pattern: /\bschema[s]?\b/i, layer: 'schema', weight: 7 },
|
|
29
|
+
{ pattern: /\bvalidat(?:or|ion)[s]?\b/i, layer: 'schema', weight: 6 },
|
|
30
|
+
{ pattern: /\butil[s]?\b|\bhelper[s]?\b|\blib\b/i, layer: 'util', weight: 6 },
|
|
31
|
+
{ pattern: /\bconfig\b|\bsettings?\b|\benv\b/i, layer: 'config', weight: 7 },
|
|
32
|
+
{ pattern: /\btest[s]?\b|\bspec[s]?\b|__tests__/i, layer: 'test', weight: 9 },
|
|
33
|
+
{ pattern: /\bmigrat(?:ion|e)[s]?\b|\bseed[s]?\b/i, layer: 'migration', weight: 8 },
|
|
34
|
+
{ pattern: /\btype[s]?\b|\binterface[s]?\b|\bd\.ts$/i, layer: 'type', weight: 7 },
|
|
35
|
+
{ pattern: /\bindex\.(ts|js)$/, layer: 'entry', weight: 3 },
|
|
36
|
+
];
|
|
37
|
+
/** Content signals for layer classification */
|
|
38
|
+
const CONTENT_SIGNALS = [
|
|
39
|
+
// Route/Controller
|
|
40
|
+
{ pattern: /Router\(\)|app\.(get|post|put|delete|patch|use)\s*\(/, layer: 'route', weight: 7, signal: 'Express router/app usage' },
|
|
41
|
+
{ pattern: /\@(Get|Post|Put|Delete|Patch|Controller)\(/, layer: 'controller', weight: 8, signal: 'Decorator-based controller' },
|
|
42
|
+
{ pattern: /req\s*,\s*res\s*[,)]|request\s*,\s*response/, layer: 'controller', weight: 5, signal: 'req/res handler signature' },
|
|
43
|
+
// Middleware
|
|
44
|
+
{ pattern: /\(req,\s*res,\s*next\)|\(req:\s*Request.*next:\s*NextFunction\)/, layer: 'middleware', weight: 8, signal: 'next() middleware signature' },
|
|
45
|
+
{ pattern: /authenticate|authorize|guard|protect/, layer: 'middleware', weight: 5, signal: 'Auth-related naming' },
|
|
46
|
+
// Service
|
|
47
|
+
{ pattern: /class\s+\w+Service\b/, layer: 'service', weight: 8, signal: 'Service class naming' },
|
|
48
|
+
{ pattern: /async\s+\w+\(.*\).*Promise</, layer: 'service', weight: 2, signal: 'Async method' },
|
|
49
|
+
// Repository/DAL
|
|
50
|
+
{ pattern: /class\s+\w+Repo(?:sitory)?\b/, layer: 'repository', weight: 8, signal: 'Repository class naming' },
|
|
51
|
+
{ pattern: /\.find(?:One|Many|All|By)\b|\.create\b|\.update\b|\.delete\b|\.save\b/, layer: 'repository', weight: 5, signal: 'CRUD methods' },
|
|
52
|
+
{ pattern: /SELECT\s|INSERT\s|UPDATE\s|DELETE\s|FROM\s/i, layer: 'repository', weight: 7, signal: 'Raw SQL' },
|
|
53
|
+
{ pattern: /prisma\.|drizzle\.|knex\.|sequelize\.|mongoose\./, layer: 'repository', weight: 8, signal: 'ORM usage' },
|
|
54
|
+
// Model/Entity
|
|
55
|
+
{ pattern: /class\s+\w+(?:Entity|Model)\b/, layer: 'model', weight: 8, signal: 'Entity/Model class naming' },
|
|
56
|
+
{ pattern: /\@Entity\(|\@Table\(|\@Column\(/, layer: 'model', weight: 9, signal: 'ORM decorators' },
|
|
57
|
+
// Schema/Validation
|
|
58
|
+
{ pattern: /z\.object\(|z\.string\(|z\.number\(/, layer: 'schema', weight: 8, signal: 'Zod schema' },
|
|
59
|
+
{ pattern: /Joi\.|yup\.|ajv\b/, layer: 'schema', weight: 7, signal: 'Validation library' },
|
|
60
|
+
// Config
|
|
61
|
+
{ pattern: /process\.env\[|dotenv|\.config\(\)/, layer: 'config', weight: 5, signal: 'Env variable access' },
|
|
62
|
+
{ pattern: /export\s+(?:const|let)\s+(?:config|settings|options)\s*=/, layer: 'config', weight: 6, signal: 'Config export' },
|
|
63
|
+
];
|
|
64
|
+
/** Architecture decision keywords to search in code */
|
|
65
|
+
const DECISION_EVIDENCE_PATTERNS = {
|
|
66
|
+
'jwt': [/jsonwebtoken|jwt\.sign|jwt\.verify|Bearer\s/i, /JwtStrategy|passport-jwt/i],
|
|
67
|
+
'session': [/express-session|cookie-session|req\.session/i],
|
|
68
|
+
'oauth': [/passport-google|passport-github|OAuth2Client/i],
|
|
69
|
+
'postgresql': [/pg\b|postgres|PG_|DATABASE_URL.*postgres/i, /prisma.*postgresql|drizzle.*pg/i],
|
|
70
|
+
'mongodb': [/mongoose|MongoClient|mongodb:\/\//i],
|
|
71
|
+
'sqlite': [/better-sqlite3|sqlite3|\.sqlite\b/i],
|
|
72
|
+
'rest': [/Router\(\)|app\.(get|post|put|delete)\(/i],
|
|
73
|
+
'graphql': [/graphql|apollo|type\s+Query\s*{|gql`/i],
|
|
74
|
+
'trpc': [/createTRPCRouter|initTRPC|tRPC/i],
|
|
75
|
+
'react': [/import.*from\s+['"]react['"]/i, /jsx|tsx|useState|useEffect/i],
|
|
76
|
+
'nextjs': [/next\/|getServerSideProps|getStaticProps|app\/.*page\.tsx/i],
|
|
77
|
+
'docker': [/Dockerfile|docker-compose|DOCKER_/i],
|
|
78
|
+
};
|
|
79
|
+
const SOURCE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs']);
|
|
80
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next']);
|
|
81
|
+
// ═══════════════════════════════════════════════════════════
|
|
82
|
+
// Main Class
|
|
83
|
+
// ═══════════════════════════════════════════════════════════
|
|
84
|
+
export class ReverseEngineer {
|
|
85
|
+
projectRoot;
|
|
86
|
+
provider;
|
|
87
|
+
constructor(projectRoot, provider) {
|
|
88
|
+
this.projectRoot = projectRoot;
|
|
89
|
+
this.provider = provider ?? null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Full reverse engineering audit.
|
|
93
|
+
* Uses static graph data from StaticAnalyzer if provided,
|
|
94
|
+
* otherwise builds its own.
|
|
95
|
+
*/
|
|
96
|
+
async audit(_imports, _exports, edges, memoryFiles) {
|
|
97
|
+
// 1. Collect all source files
|
|
98
|
+
const files = await this.collectFiles();
|
|
99
|
+
const fileContents = await this.readFiles(files);
|
|
100
|
+
// 2. Classify every file into architectural layers
|
|
101
|
+
const classifications = await this.classifyFiles(files, fileContents);
|
|
102
|
+
// 3. Trace data flow chains
|
|
103
|
+
const dataFlows = this.traceDataFlows(classifications, fileContents, _imports, edges);
|
|
104
|
+
// 4. Detect architecture violations
|
|
105
|
+
const violations = this.detectViolations(classifications, edges ?? [], dataFlows);
|
|
106
|
+
// 5. Audit decisions against actual code
|
|
107
|
+
const decisionAudit = this.auditDecisions(memoryFiles?.decisions ?? '', memoryFiles?.architecture ?? '', fileContents);
|
|
108
|
+
// 6. Detect patterns
|
|
109
|
+
const patterns = this.detectPatterns(classifications, fileContents);
|
|
110
|
+
// 7. LLM deep audit (optional)
|
|
111
|
+
let llmViolations = [];
|
|
112
|
+
if (this.provider) {
|
|
113
|
+
llmViolations = await this.llmDeepAudit(classifications, dataFlows, memoryFiles);
|
|
114
|
+
violations.push(...llmViolations);
|
|
115
|
+
}
|
|
116
|
+
// 8. Compute summary
|
|
117
|
+
const summary = this.computeSummary(classifications, dataFlows, violations, decisionAudit);
|
|
118
|
+
return { classifications, dataFlows, violations, decisionAudit, patterns, summary };
|
|
119
|
+
}
|
|
120
|
+
// ═══════════════════════════════════════════════════════════
|
|
121
|
+
// Layer Classification
|
|
122
|
+
// ═══════════════════════════════════════════════════════════
|
|
123
|
+
async classifyFiles(files, contents) {
|
|
124
|
+
const results = [];
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
const content = contents.get(file) ?? '';
|
|
127
|
+
const scores = new Map();
|
|
128
|
+
// Path-based signals
|
|
129
|
+
for (const { pattern, layer, weight } of PATH_SIGNALS) {
|
|
130
|
+
if (pattern.test(file)) {
|
|
131
|
+
const entry = scores.get(layer) ?? { score: 0, signals: [] };
|
|
132
|
+
entry.score += weight;
|
|
133
|
+
entry.signals.push(`path: ${pattern.source}`);
|
|
134
|
+
scores.set(layer, entry);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Content-based signals
|
|
138
|
+
for (const { pattern, layer, weight, signal } of CONTENT_SIGNALS) {
|
|
139
|
+
if (pattern.test(content)) {
|
|
140
|
+
const entry = scores.get(layer) ?? { score: 0, signals: [] };
|
|
141
|
+
entry.score += weight;
|
|
142
|
+
entry.signals.push(signal);
|
|
143
|
+
scores.set(layer, entry);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Find winning layer
|
|
147
|
+
let bestLayer = 'unknown';
|
|
148
|
+
let bestScore = 0;
|
|
149
|
+
let bestSignals = [];
|
|
150
|
+
for (const [layer, { score, signals }] of scores) {
|
|
151
|
+
if (score > bestScore) {
|
|
152
|
+
bestLayer = layer;
|
|
153
|
+
bestScore = score;
|
|
154
|
+
bestSignals = signals;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Extract exports
|
|
158
|
+
const exportNames = this.extractExportNames(content);
|
|
159
|
+
const maxPossible = 20; // rough max score
|
|
160
|
+
results.push({
|
|
161
|
+
file,
|
|
162
|
+
layer: bestLayer,
|
|
163
|
+
confidence: Math.min(bestScore / maxPossible, 1),
|
|
164
|
+
signals: bestSignals,
|
|
165
|
+
exports: exportNames,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return results;
|
|
169
|
+
}
|
|
170
|
+
// ═══════════════════════════════════════════════════════════
|
|
171
|
+
// Data Flow Tracing
|
|
172
|
+
// ═══════════════════════════════════════════════════════════
|
|
173
|
+
traceDataFlows(classifications, contents, _imports, edges) {
|
|
174
|
+
const chains = [];
|
|
175
|
+
// Find all route definitions as entry points
|
|
176
|
+
const routeFiles = classifications.filter(c => c.layer === 'route' || c.layer === 'controller');
|
|
177
|
+
for (const routeFile of routeFiles) {
|
|
178
|
+
const content = contents.get(routeFile.file) ?? '';
|
|
179
|
+
// Extract route definitions: app.get('/path', handler) or router.post('/path', ...)
|
|
180
|
+
const routeRegex = /\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/gi;
|
|
181
|
+
let match;
|
|
182
|
+
while ((match = routeRegex.exec(content)) !== null) {
|
|
183
|
+
const method = match[1].toUpperCase();
|
|
184
|
+
const path = match[2];
|
|
185
|
+
const trigger = `${method} ${path}`;
|
|
186
|
+
const chain = this.buildChainFromRoute(trigger, routeFile, classifications, contents, edges ?? []);
|
|
187
|
+
chains.push(chain);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return chains;
|
|
191
|
+
}
|
|
192
|
+
buildChainFromRoute(trigger, routeFile, classifications, contents, edges) {
|
|
193
|
+
const steps = [];
|
|
194
|
+
const gaps = [];
|
|
195
|
+
let order = 0;
|
|
196
|
+
// Step 1: Route handler
|
|
197
|
+
steps.push({
|
|
198
|
+
order: order++,
|
|
199
|
+
layer: routeFile.layer,
|
|
200
|
+
file: routeFile.file,
|
|
201
|
+
symbol: trigger,
|
|
202
|
+
operation: 'receive request, dispatch to handler',
|
|
203
|
+
});
|
|
204
|
+
// Step 2: Follow imports from route file → find services, repos
|
|
205
|
+
const routeImports = edges.filter(e => e.source === routeFile.file);
|
|
206
|
+
// Find service layer
|
|
207
|
+
const serviceEdge = routeImports.find(e => {
|
|
208
|
+
const target = classifications.find(c => c.file === e.target);
|
|
209
|
+
return target?.layer === 'service';
|
|
210
|
+
});
|
|
211
|
+
if (serviceEdge) {
|
|
212
|
+
const serviceContent = contents.get(serviceEdge.target) ?? '';
|
|
213
|
+
steps.push({
|
|
214
|
+
order: order++,
|
|
215
|
+
layer: 'service',
|
|
216
|
+
file: serviceEdge.target,
|
|
217
|
+
symbol: serviceEdge.symbols.join(', '),
|
|
218
|
+
operation: this.inferOperation(serviceContent, trigger),
|
|
219
|
+
});
|
|
220
|
+
// Step 3: Follow service → repo/model
|
|
221
|
+
const serviceImports = edges.filter(e => e.source === serviceEdge.target);
|
|
222
|
+
const repoEdge = serviceImports.find(e => {
|
|
223
|
+
const target = classifications.find(c => c.file === e.target);
|
|
224
|
+
return target?.layer === 'repository' || target?.layer === 'model';
|
|
225
|
+
});
|
|
226
|
+
if (repoEdge) {
|
|
227
|
+
steps.push({
|
|
228
|
+
order: order++,
|
|
229
|
+
layer: classifications.find(c => c.file === repoEdge.target)?.layer ?? 'repository',
|
|
230
|
+
file: repoEdge.target,
|
|
231
|
+
symbol: repoEdge.symbols.join(', '),
|
|
232
|
+
operation: 'data access / persistence',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
gaps.push('Service has no repository/model dependency — data access layer missing or inline');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Controller might directly use repo (layer skip)
|
|
241
|
+
const directRepo = routeImports.find(e => {
|
|
242
|
+
const target = classifications.find(c => c.file === e.target);
|
|
243
|
+
return target?.layer === 'repository' || target?.layer === 'model';
|
|
244
|
+
});
|
|
245
|
+
if (directRepo) {
|
|
246
|
+
steps.push({
|
|
247
|
+
order: order++,
|
|
248
|
+
layer: classifications.find(c => c.file === directRepo.target)?.layer ?? 'repository',
|
|
249
|
+
file: directRepo.target,
|
|
250
|
+
symbol: directRepo.symbols.join(', '),
|
|
251
|
+
operation: 'direct data access (no service layer)',
|
|
252
|
+
});
|
|
253
|
+
gaps.push('Controller accesses repository directly — service layer skipped');
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
gaps.push('Route handler has no service or repository imports — logic is likely inline');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Middleware detection
|
|
260
|
+
const middlewareEdge = routeImports.find(e => {
|
|
261
|
+
const target = classifications.find(c => c.file === e.target);
|
|
262
|
+
return target?.layer === 'middleware';
|
|
263
|
+
});
|
|
264
|
+
if (middlewareEdge) {
|
|
265
|
+
// Insert middleware as step 1 (before service)
|
|
266
|
+
steps.splice(1, 0, {
|
|
267
|
+
order: 0,
|
|
268
|
+
layer: 'middleware',
|
|
269
|
+
file: middlewareEdge.target,
|
|
270
|
+
symbol: middlewareEdge.symbols.join(', '),
|
|
271
|
+
operation: 'pre-processing (auth, validation, etc.)',
|
|
272
|
+
});
|
|
273
|
+
// Reorder
|
|
274
|
+
steps.forEach((s, i) => s.order = i);
|
|
275
|
+
}
|
|
276
|
+
// Schema/validation detection
|
|
277
|
+
const schemaEdge = routeImports.find(e => {
|
|
278
|
+
const target = classifications.find(c => c.file === e.target);
|
|
279
|
+
return target?.layer === 'schema';
|
|
280
|
+
});
|
|
281
|
+
if (schemaEdge) {
|
|
282
|
+
steps.splice(middlewareEdge ? 2 : 1, 0, {
|
|
283
|
+
order: 0,
|
|
284
|
+
layer: 'schema',
|
|
285
|
+
file: schemaEdge.target,
|
|
286
|
+
symbol: schemaEdge.symbols.join(', '),
|
|
287
|
+
operation: 'input validation',
|
|
288
|
+
});
|
|
289
|
+
steps.forEach((s, i) => s.order = i);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
id: `flow-${trigger.replace(/\s+/g, '-').toLowerCase()}`,
|
|
293
|
+
trigger,
|
|
294
|
+
steps,
|
|
295
|
+
complete: gaps.length === 0,
|
|
296
|
+
gaps,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// ═══════════════════════════════════════════════════════════
|
|
300
|
+
// Architecture Violation Detection
|
|
301
|
+
// ═══════════════════════════════════════════════════════════
|
|
302
|
+
detectViolations(classifications, edges, dataFlows) {
|
|
303
|
+
const violations = [];
|
|
304
|
+
// 1. Layer skip: controller → repository (skipping service)
|
|
305
|
+
for (const flow of dataFlows) {
|
|
306
|
+
if (flow.gaps.some(g => g.includes('service layer skipped'))) {
|
|
307
|
+
violations.push({
|
|
308
|
+
type: 'layer-skip',
|
|
309
|
+
severity: 'warning',
|
|
310
|
+
description: `${flow.trigger}: controller accesses repository directly, service layer skipped`,
|
|
311
|
+
file: flow.steps[0]?.file ?? 'unknown',
|
|
312
|
+
evidence: flow.gaps.join('; '),
|
|
313
|
+
expectedBehavior: 'Controller → Service → Repository (3-tier architecture)',
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// 2. Wrong direction: service imports controller, repo imports service
|
|
318
|
+
const layerOrder = ['route', 'controller', 'middleware', 'service', 'repository', 'model'];
|
|
319
|
+
for (const edge of edges) {
|
|
320
|
+
const sourceClass = classifications.find(c => c.file === edge.source);
|
|
321
|
+
const targetClass = classifications.find(c => c.file === edge.target);
|
|
322
|
+
if (!sourceClass || !targetClass)
|
|
323
|
+
continue;
|
|
324
|
+
const sourceIdx = layerOrder.indexOf(sourceClass.layer);
|
|
325
|
+
const targetIdx = layerOrder.indexOf(targetClass.layer);
|
|
326
|
+
// Lower layer importing higher layer = wrong direction
|
|
327
|
+
if (sourceIdx >= 0 && targetIdx >= 0 && sourceIdx > targetIdx && targetIdx < sourceIdx - 1) {
|
|
328
|
+
violations.push({
|
|
329
|
+
type: 'wrong-direction',
|
|
330
|
+
severity: 'warning',
|
|
331
|
+
description: `${sourceClass.layer} (${edge.source}) imports ${targetClass.layer} (${edge.target}) — dependency flows upward`,
|
|
332
|
+
file: edge.source,
|
|
333
|
+
evidence: `import { ${edge.symbols.join(', ')} } from '${edge.target}'`,
|
|
334
|
+
expectedBehavior: 'Dependencies should flow downward: controller → service → repository',
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// 3. Incomplete data flows
|
|
339
|
+
for (const flow of dataFlows) {
|
|
340
|
+
if (!flow.complete) {
|
|
341
|
+
violations.push({
|
|
342
|
+
type: 'coupling-violation',
|
|
343
|
+
severity: 'info',
|
|
344
|
+
description: `${flow.trigger}: data flow chain is incomplete`,
|
|
345
|
+
file: flow.steps[0]?.file ?? 'unknown',
|
|
346
|
+
evidence: flow.gaps.join('; '),
|
|
347
|
+
expectedBehavior: 'Complete chain: route → middleware → service → repository → response',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// 4. Pattern inconsistency: some routes have service layer, others don't
|
|
352
|
+
const routesWithService = dataFlows.filter(f => f.steps.some(s => s.layer === 'service'));
|
|
353
|
+
const routesWithoutService = dataFlows.filter(f => !f.steps.some(s => s.layer === 'service') && f.steps.length > 1);
|
|
354
|
+
if (routesWithService.length > 0 && routesWithoutService.length > 0) {
|
|
355
|
+
violations.push({
|
|
356
|
+
type: 'pattern-inconsistency',
|
|
357
|
+
severity: 'warning',
|
|
358
|
+
description: `Mixed patterns: ${routesWithService.length} routes use service layer, ${routesWithoutService.length} don't`,
|
|
359
|
+
file: 'project-wide',
|
|
360
|
+
evidence: `Without service: ${routesWithoutService.map(f => f.trigger).join(', ')}`,
|
|
361
|
+
expectedBehavior: 'All routes should consistently use (or not use) a service layer',
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return violations;
|
|
365
|
+
}
|
|
366
|
+
// ═══════════════════════════════════════════════════════════
|
|
367
|
+
// Decision Archaeology
|
|
368
|
+
// ═══════════════════════════════════════════════════════════
|
|
369
|
+
auditDecisions(decisionsContent, architectureContent, fileContents) {
|
|
370
|
+
const results = [];
|
|
371
|
+
// Parse decisions from DECISIONS.md
|
|
372
|
+
const decisionBlocks = decisionsContent.split(/(?=## D\d{3})/).filter(d => /^## D\d{3}/.test(d));
|
|
373
|
+
for (const block of decisionBlocks) {
|
|
374
|
+
const idMatch = block.match(/## (D\d{3})\s*[—–-]\s*(.+)/);
|
|
375
|
+
if (!idMatch)
|
|
376
|
+
continue;
|
|
377
|
+
const id = idMatch[1];
|
|
378
|
+
const title = idMatch[2].trim();
|
|
379
|
+
// Find keywords from decision title and body
|
|
380
|
+
const keywords = this.extractDecisionKeywords(title, block);
|
|
381
|
+
const evidence = [];
|
|
382
|
+
const files = [];
|
|
383
|
+
for (const [file, content] of fileContents) {
|
|
384
|
+
for (const keyword of keywords) {
|
|
385
|
+
if (content.toLowerCase().includes(keyword.toLowerCase())) {
|
|
386
|
+
evidence.push(`${file}: contains "${keyword}"`);
|
|
387
|
+
if (!files.includes(file))
|
|
388
|
+
files.push(file);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let status = 'not-found';
|
|
393
|
+
if (evidence.length >= 3)
|
|
394
|
+
status = 'implemented';
|
|
395
|
+
else if (evidence.length >= 1)
|
|
396
|
+
status = 'partially-implemented';
|
|
397
|
+
results.push({ decisionId: id, title, status, evidence: evidence.slice(0, 10), files });
|
|
398
|
+
}
|
|
399
|
+
// Parse architecture decisions
|
|
400
|
+
const archDecisions = this.extractArchDecisions(architectureContent);
|
|
401
|
+
for (const [key, value] of archDecisions) {
|
|
402
|
+
const patterns = DECISION_EVIDENCE_PATTERNS[value.toLowerCase()];
|
|
403
|
+
if (!patterns)
|
|
404
|
+
continue;
|
|
405
|
+
const evidence = [];
|
|
406
|
+
const files = [];
|
|
407
|
+
for (const [file, content] of fileContents) {
|
|
408
|
+
for (const pattern of patterns) {
|
|
409
|
+
if (pattern.test(content)) {
|
|
410
|
+
evidence.push(`${file}: matches ${pattern.source.slice(0, 50)}`);
|
|
411
|
+
if (!files.includes(file))
|
|
412
|
+
files.push(file);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Check for contradictions — e.g., ARCHITECTURE says JWT but code uses session
|
|
417
|
+
const contradictions = this.findContradictions(key, value, fileContents);
|
|
418
|
+
let status = evidence.length > 0 ? 'implemented' : 'not-found';
|
|
419
|
+
if (contradictions.length > 0)
|
|
420
|
+
status = 'contradicted';
|
|
421
|
+
results.push({
|
|
422
|
+
decisionId: `ARCH-${key}`,
|
|
423
|
+
title: `${key}: ${value}`,
|
|
424
|
+
status,
|
|
425
|
+
evidence: [...evidence.slice(0, 5), ...contradictions.map(c => `⚠️ CONTRADICTION: ${c}`)],
|
|
426
|
+
files,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return results;
|
|
430
|
+
}
|
|
431
|
+
findContradictions(key, value, fileContents) {
|
|
432
|
+
const contradictions = [];
|
|
433
|
+
const allCode = [...fileContents.entries()];
|
|
434
|
+
// Auth contradictions
|
|
435
|
+
if (key === 'auth' || key === 'Auth') {
|
|
436
|
+
const alternatives = {
|
|
437
|
+
'jwt': [/express-session|cookie-session|req\.session/],
|
|
438
|
+
'session': [/jsonwebtoken|jwt\.sign|jwt\.verify/],
|
|
439
|
+
'none': [/jsonwebtoken|passport|express-session/],
|
|
440
|
+
};
|
|
441
|
+
const contras = alternatives[value.toLowerCase()];
|
|
442
|
+
if (contras) {
|
|
443
|
+
for (const [file, content] of allCode) {
|
|
444
|
+
for (const pattern of contras) {
|
|
445
|
+
if (pattern.test(content)) {
|
|
446
|
+
contradictions.push(`${file} uses ${pattern.source.slice(0, 40)} but ARCHITECTURE says ${key}: ${value}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Database contradictions
|
|
453
|
+
if (key === 'database' || key === 'Database') {
|
|
454
|
+
const alternatives = {
|
|
455
|
+
'postgresql': [/mongoose|MongoClient|mongodb/i, /better-sqlite3|sqlite3/i],
|
|
456
|
+
'mongodb': [/pg\b|postgres|PG_/i, /better-sqlite3|sqlite3/i],
|
|
457
|
+
'sqlite': [/mongoose|MongoClient|mongodb/i, /pg\b|postgres|PG_/i],
|
|
458
|
+
'in-memory': [/pg\b|postgres|mongoose|MongoClient|sqlite/i],
|
|
459
|
+
};
|
|
460
|
+
const contras = alternatives[value.toLowerCase()];
|
|
461
|
+
if (contras) {
|
|
462
|
+
for (const [file, content] of allCode) {
|
|
463
|
+
for (const pattern of contras) {
|
|
464
|
+
if (pattern.test(content)) {
|
|
465
|
+
contradictions.push(`${file} uses ${pattern.source.slice(0, 40)} but ARCHITECTURE says ${key}: ${value}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return contradictions;
|
|
472
|
+
}
|
|
473
|
+
// ═══════════════════════════════════════════════════════════
|
|
474
|
+
// Pattern Detection
|
|
475
|
+
// ═══════════════════════════════════════════════════════════
|
|
476
|
+
detectPatterns(classifications, contents) {
|
|
477
|
+
const patterns = [];
|
|
478
|
+
const allCode = [...contents.values()].join('\n');
|
|
479
|
+
// Repository pattern
|
|
480
|
+
const repoFiles = classifications.filter(c => c.layer === 'repository');
|
|
481
|
+
if (repoFiles.length > 0) {
|
|
482
|
+
patterns.push({ name: 'Repository Pattern', files: repoFiles.map(f => f.file), confidence: 0.9 });
|
|
483
|
+
}
|
|
484
|
+
// Service layer pattern
|
|
485
|
+
const serviceFiles = classifications.filter(c => c.layer === 'service');
|
|
486
|
+
if (serviceFiles.length > 0) {
|
|
487
|
+
patterns.push({ name: 'Service Layer', files: serviceFiles.map(f => f.file), confidence: 0.9 });
|
|
488
|
+
}
|
|
489
|
+
// Middleware chain
|
|
490
|
+
const middlewareFiles = classifications.filter(c => c.layer === 'middleware');
|
|
491
|
+
if (middlewareFiles.length > 0) {
|
|
492
|
+
patterns.push({ name: 'Middleware Chain', files: middlewareFiles.map(f => f.file), confidence: 0.8 });
|
|
493
|
+
}
|
|
494
|
+
// Factory pattern
|
|
495
|
+
if (/create\w+\(|factory|Factory/.test(allCode)) {
|
|
496
|
+
const factoryFiles = [...contents.entries()]
|
|
497
|
+
.filter(([_, c]) => /create\w+\(|class\s+\w+Factory/.test(c))
|
|
498
|
+
.map(([f]) => f);
|
|
499
|
+
if (factoryFiles.length > 0) {
|
|
500
|
+
patterns.push({ name: 'Factory Pattern', files: factoryFiles, confidence: 0.7 });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Singleton pattern
|
|
504
|
+
if (/(?:let|const)\s+instance\b|getInstance\(\)/.test(allCode)) {
|
|
505
|
+
const singletonFiles = [...contents.entries()]
|
|
506
|
+
.filter(([_, c]) => /getInstance\(\)|private\s+static\s+instance/.test(c))
|
|
507
|
+
.map(([f]) => f);
|
|
508
|
+
if (singletonFiles.length > 0) {
|
|
509
|
+
patterns.push({ name: 'Singleton Pattern', files: singletonFiles, confidence: 0.8 });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Dependency injection
|
|
513
|
+
if (/constructor\s*\(\s*(?:private|readonly)/.test(allCode)) {
|
|
514
|
+
const diFiles = [...contents.entries()]
|
|
515
|
+
.filter(([_, c]) => /constructor\s*\(\s*(?:private|readonly)\s+\w+:\s+\w+/.test(c))
|
|
516
|
+
.map(([f]) => f);
|
|
517
|
+
if (diFiles.length > 1) {
|
|
518
|
+
patterns.push({ name: 'Constructor Injection', files: diFiles, confidence: 0.7 });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return patterns;
|
|
522
|
+
}
|
|
523
|
+
// ═══════════════════════════════════════════════════════════
|
|
524
|
+
// LLM Deep Audit
|
|
525
|
+
// ═══════════════════════════════════════════════════════════
|
|
526
|
+
async llmDeepAudit(classifications, dataFlows, memoryFiles) {
|
|
527
|
+
if (!this.provider)
|
|
528
|
+
return [];
|
|
529
|
+
const classificationSummary = classifications
|
|
530
|
+
.filter(c => c.layer !== 'test' && c.layer !== 'type')
|
|
531
|
+
.map(c => `${c.file} → ${c.layer} (${(c.confidence * 100).toFixed(0)}%)`)
|
|
532
|
+
.join('\n');
|
|
533
|
+
const flowSummary = dataFlows
|
|
534
|
+
.map(f => `${f.trigger}: ${f.steps.map(s => `${s.layer}(${s.file})`).join(' → ')}${f.complete ? '' : ' [INCOMPLETE]'}`)
|
|
535
|
+
.join('\n');
|
|
536
|
+
const prompt = `You are auditing a codebase for architectural consistency.
|
|
537
|
+
|
|
538
|
+
## File Classifications
|
|
539
|
+
${classificationSummary}
|
|
540
|
+
|
|
541
|
+
## Data Flow Chains
|
|
542
|
+
${flowSummary}
|
|
543
|
+
|
|
544
|
+
${memoryFiles?.architecture ? `## ARCHITECTURE.md\n${memoryFiles.architecture.slice(0, 2000)}` : ''}
|
|
545
|
+
${memoryFiles?.decisions ? `## DECISIONS.md (recent)\n${memoryFiles.decisions.slice(-2000)}` : ''}
|
|
546
|
+
${memoryFiles?.mission ? `## MISSION.md\n${memoryFiles.mission.slice(0, 1000)}` : ''}
|
|
547
|
+
|
|
548
|
+
Identify architectural violations. Return JSON array:
|
|
549
|
+
[{
|
|
550
|
+
"type": "layer-skip|wrong-direction|decision-missing|decision-contradicted|pattern-inconsistency|coupling-violation",
|
|
551
|
+
"severity": "critical|warning|info",
|
|
552
|
+
"description": "...",
|
|
553
|
+
"file": "...",
|
|
554
|
+
"evidence": "...",
|
|
555
|
+
"expectedBehavior": "..."
|
|
556
|
+
}]
|
|
557
|
+
|
|
558
|
+
Only report issues you're confident about. Empty array if none found.`;
|
|
559
|
+
try {
|
|
560
|
+
const response = await this.provider.chat([{ role: 'user', content: prompt }], { system: 'You are a software architecture auditor. Report only real violations with evidence.', maxTokens: 4096 });
|
|
561
|
+
const jsonMatch = response.text.match(/\[[\s\S]*\]/);
|
|
562
|
+
if (!jsonMatch)
|
|
563
|
+
return [];
|
|
564
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
565
|
+
return parsed.filter(v => v.type && v.description && v.file);
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// ═══════════════════════════════════════════════════════════
|
|
572
|
+
// Summary Computation
|
|
573
|
+
// ═══════════════════════════════════════════════════════════
|
|
574
|
+
computeSummary(classifications, dataFlows, violations, decisionAudit) {
|
|
575
|
+
const layerDist = {};
|
|
576
|
+
for (const c of classifications) {
|
|
577
|
+
layerDist[c.layer] = (layerDist[c.layer] ?? 0) + 1;
|
|
578
|
+
}
|
|
579
|
+
const decisionsImpl = decisionAudit.filter(d => d.status === 'implemented').length;
|
|
580
|
+
const completeFlows = dataFlows.filter(f => f.complete).length;
|
|
581
|
+
// Health score: 100 base, subtract for issues
|
|
582
|
+
let health = 100;
|
|
583
|
+
health -= violations.filter(v => v.severity === 'critical').length * 15;
|
|
584
|
+
health -= violations.filter(v => v.severity === 'warning').length * 5;
|
|
585
|
+
health -= violations.filter(v => v.severity === 'info').length * 1;
|
|
586
|
+
health -= decisionAudit.filter(d => d.status === 'contradicted').length * 20;
|
|
587
|
+
health -= decisionAudit.filter(d => d.status === 'not-found').length * 5;
|
|
588
|
+
const incompleteRatio = dataFlows.length > 0
|
|
589
|
+
? (dataFlows.length - completeFlows) / dataFlows.length
|
|
590
|
+
: 0;
|
|
591
|
+
health -= Math.round(incompleteRatio * 20);
|
|
592
|
+
return {
|
|
593
|
+
totalFiles: classifications.length,
|
|
594
|
+
layerDistribution: layerDist,
|
|
595
|
+
totalFlows: dataFlows.length,
|
|
596
|
+
completeFlows,
|
|
597
|
+
incompleteFlows: dataFlows.length - completeFlows,
|
|
598
|
+
violationCount: violations.length,
|
|
599
|
+
decisionsImplemented: decisionsImpl,
|
|
600
|
+
decisionsTotal: decisionAudit.length,
|
|
601
|
+
healthScore: Math.max(0, Math.min(100, health)),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
// ═══════════════════════════════════════════════════════════
|
|
605
|
+
// Helpers
|
|
606
|
+
// ═══════════════════════════════════════════════════════════
|
|
607
|
+
async collectFiles() {
|
|
608
|
+
const files = [];
|
|
609
|
+
await this.walk(this.projectRoot, files);
|
|
610
|
+
return files;
|
|
611
|
+
}
|
|
612
|
+
async walk(dir, files) {
|
|
613
|
+
let entries;
|
|
614
|
+
try {
|
|
615
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
for (const entry of entries) {
|
|
621
|
+
if (entry.isDirectory()) {
|
|
622
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.'))
|
|
623
|
+
continue;
|
|
624
|
+
await this.walk(join(dir, entry.name), files);
|
|
625
|
+
}
|
|
626
|
+
else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name).toLowerCase())) {
|
|
627
|
+
files.push(relative(this.projectRoot, join(dir, entry.name)).replace(/\\/g, '/'));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async readFiles(files) {
|
|
632
|
+
const map = new Map();
|
|
633
|
+
for (const file of files) {
|
|
634
|
+
try {
|
|
635
|
+
map.set(file, await readFile(join(this.projectRoot, file), 'utf-8'));
|
|
636
|
+
}
|
|
637
|
+
catch { /* skip unreadable */ }
|
|
638
|
+
}
|
|
639
|
+
return map;
|
|
640
|
+
}
|
|
641
|
+
extractExportNames(content) {
|
|
642
|
+
const exports = [];
|
|
643
|
+
const regex = /export\s+(?:default\s+)?(?:class|function|const|let|var|type|interface|enum)\s+(\w+)/g;
|
|
644
|
+
let m;
|
|
645
|
+
while ((m = regex.exec(content)) !== null) {
|
|
646
|
+
if (m[1])
|
|
647
|
+
exports.push(m[1]);
|
|
648
|
+
}
|
|
649
|
+
return exports;
|
|
650
|
+
}
|
|
651
|
+
extractDecisionKeywords(title, block) {
|
|
652
|
+
// Extract meaningful words from decision title and body
|
|
653
|
+
const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
654
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may',
|
|
655
|
+
'might', 'shall', 'can', 'need', 'must', 'for', 'and', 'nor', 'but', 'or', 'yet', 'so',
|
|
656
|
+
'at', 'by', 'in', 'of', 'on', 'to', 'with', 'from', 'up', 'about', 'into', 'through',
|
|
657
|
+
'during', 'before', 'after', 'above', 'below', 'between', 'this', 'that', 'these', 'those',
|
|
658
|
+
'bir', 've', 'ile', 'için', 'olan', 'olarak', 'bu', 'şu', 'karar', 'tarih', 'durum', 'active']);
|
|
659
|
+
const words = (title + ' ' + block)
|
|
660
|
+
.split(/[\s,.;:!?/\\()\[\]{}"'`—–-]+/)
|
|
661
|
+
.map(w => w.toLowerCase())
|
|
662
|
+
.filter(w => w.length > 3 && !stopWords.has(w) && !/^\d+$/.test(w));
|
|
663
|
+
return [...new Set(words)].slice(0, 15);
|
|
664
|
+
}
|
|
665
|
+
extractArchDecisions(architecture) {
|
|
666
|
+
const decisions = new Map();
|
|
667
|
+
// Parse "**Key**: value" pattern from architecture
|
|
668
|
+
const regex = /\*\*(\w+)\*\*:\s*(\S+)/g;
|
|
669
|
+
let m;
|
|
670
|
+
while ((m = regex.exec(architecture)) !== null) {
|
|
671
|
+
if (m[1] && m[2])
|
|
672
|
+
decisions.set(m[1], m[2]);
|
|
673
|
+
}
|
|
674
|
+
return decisions;
|
|
675
|
+
}
|
|
676
|
+
inferOperation(content, trigger) {
|
|
677
|
+
const lower = content.toLowerCase();
|
|
678
|
+
if (trigger.startsWith('POST') && (lower.includes('create') || lower.includes('insert')))
|
|
679
|
+
return 'create resource';
|
|
680
|
+
if (trigger.startsWith('GET') && (lower.includes('findall') || lower.includes('list') || lower.includes('getall')))
|
|
681
|
+
return 'list resources';
|
|
682
|
+
if (trigger.startsWith('GET') && (lower.includes('findone') || lower.includes('getby')))
|
|
683
|
+
return 'get single resource';
|
|
684
|
+
if (trigger.startsWith('PUT') || trigger.startsWith('PATCH'))
|
|
685
|
+
return 'update resource';
|
|
686
|
+
if (trigger.startsWith('DELETE'))
|
|
687
|
+
return 'delete resource';
|
|
688
|
+
if (lower.includes('login') || lower.includes('auth'))
|
|
689
|
+
return 'authenticate';
|
|
690
|
+
if (lower.includes('register') || lower.includes('signup'))
|
|
691
|
+
return 'register user';
|
|
692
|
+
return 'business logic';
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
//# sourceMappingURL=reverse-engineer.js.map
|