@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.
Files changed (200) hide show
  1. package/.env.example +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +364 -0
  4. package/dist/agent/agent-runner.d.ts +48 -0
  5. package/dist/agent/agent-runner.d.ts.map +1 -0
  6. package/dist/agent/agent-runner.js +180 -0
  7. package/dist/agent/agent-runner.js.map +1 -0
  8. package/dist/agent/architect.d.ts +34 -0
  9. package/dist/agent/architect.d.ts.map +1 -0
  10. package/dist/agent/architect.js +156 -0
  11. package/dist/agent/architect.js.map +1 -0
  12. package/dist/agent/codebase-reader.d.ts +35 -0
  13. package/dist/agent/codebase-reader.d.ts.map +1 -0
  14. package/dist/agent/codebase-reader.js +305 -0
  15. package/dist/agent/codebase-reader.js.map +1 -0
  16. package/dist/agent/context-builder.d.ts +39 -0
  17. package/dist/agent/context-builder.d.ts.map +1 -0
  18. package/dist/agent/context-builder.js +163 -0
  19. package/dist/agent/context-builder.js.map +1 -0
  20. package/dist/agent/index.d.ts +14 -0
  21. package/dist/agent/index.d.ts.map +1 -0
  22. package/dist/agent/index.js +12 -0
  23. package/dist/agent/index.js.map +1 -0
  24. package/dist/agent/output-parser.d.ts +27 -0
  25. package/dist/agent/output-parser.d.ts.map +1 -0
  26. package/dist/agent/output-parser.js +154 -0
  27. package/dist/agent/output-parser.js.map +1 -0
  28. package/dist/agent/process-spawner.d.ts +56 -0
  29. package/dist/agent/process-spawner.d.ts.map +1 -0
  30. package/dist/agent/process-spawner.js +153 -0
  31. package/dist/agent/process-spawner.js.map +1 -0
  32. package/dist/agent/tracer/index.d.ts +8 -0
  33. package/dist/agent/tracer/index.d.ts.map +1 -0
  34. package/dist/agent/tracer/index.js +6 -0
  35. package/dist/agent/tracer/index.js.map +1 -0
  36. package/dist/agent/tracer/reverse-engineer.d.ts +113 -0
  37. package/dist/agent/tracer/reverse-engineer.d.ts.map +1 -0
  38. package/dist/agent/tracer/reverse-engineer.js +695 -0
  39. package/dist/agent/tracer/reverse-engineer.js.map +1 -0
  40. package/dist/agent/tracer/runtime-tracer.d.ts +61 -0
  41. package/dist/agent/tracer/runtime-tracer.d.ts.map +1 -0
  42. package/dist/agent/tracer/runtime-tracer.js +484 -0
  43. package/dist/agent/tracer/runtime-tracer.js.map +1 -0
  44. package/dist/agent/tracer/semantic-analyzer.d.ts +24 -0
  45. package/dist/agent/tracer/semantic-analyzer.d.ts.map +1 -0
  46. package/dist/agent/tracer/semantic-analyzer.js +132 -0
  47. package/dist/agent/tracer/semantic-analyzer.js.map +1 -0
  48. package/dist/agent/tracer/static-analyzer.d.ts +44 -0
  49. package/dist/agent/tracer/static-analyzer.d.ts.map +1 -0
  50. package/dist/agent/tracer/static-analyzer.js +453 -0
  51. package/dist/agent/tracer/static-analyzer.js.map +1 -0
  52. package/dist/agent/tracer/tracer-agent.d.ts +61 -0
  53. package/dist/agent/tracer/tracer-agent.d.ts.map +1 -0
  54. package/dist/agent/tracer/tracer-agent.js +252 -0
  55. package/dist/agent/tracer/tracer-agent.js.map +1 -0
  56. package/dist/bin/csns.d.ts +24 -0
  57. package/dist/bin/csns.d.ts.map +1 -0
  58. package/dist/bin/csns.js +389 -0
  59. package/dist/bin/csns.js.map +1 -0
  60. package/dist/bin/pc.d.ts +13 -0
  61. package/dist/bin/pc.d.ts.map +1 -0
  62. package/dist/bin/pc.js +212 -0
  63. package/dist/bin/pc.js.map +1 -0
  64. package/dist/brief/brief-collector.d.ts +42 -0
  65. package/dist/brief/brief-collector.d.ts.map +1 -0
  66. package/dist/brief/brief-collector.js +228 -0
  67. package/dist/brief/brief-collector.js.map +1 -0
  68. package/dist/brief/index.d.ts +3 -0
  69. package/dist/brief/index.d.ts.map +1 -0
  70. package/dist/brief/index.js +3 -0
  71. package/dist/brief/index.js.map +1 -0
  72. package/dist/brief/smart-brief.d.ts +52 -0
  73. package/dist/brief/smart-brief.d.ts.map +1 -0
  74. package/dist/brief/smart-brief.js +440 -0
  75. package/dist/brief/smart-brief.js.map +1 -0
  76. package/dist/calculator/calculator.d.ts +7 -0
  77. package/dist/calculator/calculator.d.ts.map +1 -0
  78. package/dist/calculator/calculator.js +18 -0
  79. package/dist/calculator/calculator.js.map +1 -0
  80. package/dist/calculator/index.d.ts +2 -0
  81. package/dist/calculator/index.d.ts.map +1 -0
  82. package/dist/calculator/index.js +2 -0
  83. package/dist/calculator/index.js.map +1 -0
  84. package/dist/i18n/en.d.ts +6 -0
  85. package/dist/i18n/en.d.ts.map +1 -0
  86. package/dist/i18n/en.js +136 -0
  87. package/dist/i18n/en.js.map +1 -0
  88. package/dist/i18n/index.d.ts +31 -0
  89. package/dist/i18n/index.d.ts.map +1 -0
  90. package/dist/i18n/index.js +44 -0
  91. package/dist/i18n/index.js.map +1 -0
  92. package/dist/i18n/tr.d.ts +6 -0
  93. package/dist/i18n/tr.d.ts.map +1 -0
  94. package/dist/i18n/tr.js +136 -0
  95. package/dist/i18n/tr.js.map +1 -0
  96. package/dist/i18n/types.d.ts +86 -0
  97. package/dist/i18n/types.d.ts.map +1 -0
  98. package/dist/i18n/types.js +9 -0
  99. package/dist/i18n/types.js.map +1 -0
  100. package/dist/index.d.ts +7 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +72 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/llm/anthropic-provider.d.ts +18 -0
  105. package/dist/llm/anthropic-provider.d.ts.map +1 -0
  106. package/dist/llm/anthropic-provider.js +55 -0
  107. package/dist/llm/anthropic-provider.js.map +1 -0
  108. package/dist/llm/factory.d.ts +21 -0
  109. package/dist/llm/factory.d.ts.map +1 -0
  110. package/dist/llm/factory.js +59 -0
  111. package/dist/llm/factory.js.map +1 -0
  112. package/dist/llm/index.d.ts +7 -0
  113. package/dist/llm/index.d.ts.map +1 -0
  114. package/dist/llm/index.js +5 -0
  115. package/dist/llm/index.js.map +1 -0
  116. package/dist/llm/ollama-provider.d.ts +20 -0
  117. package/dist/llm/ollama-provider.d.ts.map +1 -0
  118. package/dist/llm/ollama-provider.js +62 -0
  119. package/dist/llm/ollama-provider.js.map +1 -0
  120. package/dist/llm/openai-provider.d.ts +20 -0
  121. package/dist/llm/openai-provider.d.ts.map +1 -0
  122. package/dist/llm/openai-provider.js +65 -0
  123. package/dist/llm/openai-provider.js.map +1 -0
  124. package/dist/llm/resolve.d.ts +10 -0
  125. package/dist/llm/resolve.d.ts.map +1 -0
  126. package/dist/llm/resolve.js +21 -0
  127. package/dist/llm/resolve.js.map +1 -0
  128. package/dist/llm/types.d.ts +45 -0
  129. package/dist/llm/types.d.ts.map +1 -0
  130. package/dist/llm/types.js +11 -0
  131. package/dist/llm/types.js.map +1 -0
  132. package/dist/memory/index.d.ts +2 -0
  133. package/dist/memory/index.d.ts.map +1 -0
  134. package/dist/memory/index.js +2 -0
  135. package/dist/memory/index.js.map +1 -0
  136. package/dist/memory/memory-layer.d.ts +54 -0
  137. package/dist/memory/memory-layer.d.ts.map +1 -0
  138. package/dist/memory/memory-layer.js +297 -0
  139. package/dist/memory/memory-layer.js.map +1 -0
  140. package/dist/orchestrator/dependency-graph.d.ts +39 -0
  141. package/dist/orchestrator/dependency-graph.d.ts.map +1 -0
  142. package/dist/orchestrator/dependency-graph.js +134 -0
  143. package/dist/orchestrator/dependency-graph.js.map +1 -0
  144. package/dist/orchestrator/escalator.d.ts +42 -0
  145. package/dist/orchestrator/escalator.d.ts.map +1 -0
  146. package/dist/orchestrator/escalator.js +163 -0
  147. package/dist/orchestrator/escalator.js.map +1 -0
  148. package/dist/orchestrator/evaluator.d.ts +34 -0
  149. package/dist/orchestrator/evaluator.d.ts.map +1 -0
  150. package/dist/orchestrator/evaluator.js +335 -0
  151. package/dist/orchestrator/evaluator.js.map +1 -0
  152. package/dist/orchestrator/index.d.ts +9 -0
  153. package/dist/orchestrator/index.d.ts.map +1 -0
  154. package/dist/orchestrator/index.js +9 -0
  155. package/dist/orchestrator/index.js.map +1 -0
  156. package/dist/orchestrator/integration-evaluator.d.ts +59 -0
  157. package/dist/orchestrator/integration-evaluator.d.ts.map +1 -0
  158. package/dist/orchestrator/integration-evaluator.js +405 -0
  159. package/dist/orchestrator/integration-evaluator.js.map +1 -0
  160. package/dist/orchestrator/milestone-manager.d.ts +28 -0
  161. package/dist/orchestrator/milestone-manager.d.ts.map +1 -0
  162. package/dist/orchestrator/milestone-manager.js +208 -0
  163. package/dist/orchestrator/milestone-manager.js.map +1 -0
  164. package/dist/orchestrator/orchestrator.d.ts +35 -0
  165. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  166. package/dist/orchestrator/orchestrator.js +338 -0
  167. package/dist/orchestrator/orchestrator.js.map +1 -0
  168. package/dist/orchestrator/planner.d.ts +15 -0
  169. package/dist/orchestrator/planner.d.ts.map +1 -0
  170. package/dist/orchestrator/planner.js +87 -0
  171. package/dist/orchestrator/planner.js.map +1 -0
  172. package/dist/orchestrator/recovery.d.ts +45 -0
  173. package/dist/orchestrator/recovery.d.ts.map +1 -0
  174. package/dist/orchestrator/recovery.js +98 -0
  175. package/dist/orchestrator/recovery.js.map +1 -0
  176. package/dist/run-calculator-test.d.ts +11 -0
  177. package/dist/run-calculator-test.d.ts.map +1 -0
  178. package/dist/run-calculator-test.js +212 -0
  179. package/dist/run-calculator-test.js.map +1 -0
  180. package/dist/run-real-task.d.ts +14 -0
  181. package/dist/run-real-task.d.ts.map +1 -0
  182. package/dist/run-real-task.js +185 -0
  183. package/dist/run-real-task.js.map +1 -0
  184. package/dist/run-todo-test.d.ts +6 -0
  185. package/dist/run-todo-test.d.ts.map +1 -0
  186. package/dist/run-todo-test.js +149 -0
  187. package/dist/run-todo-test.js.map +1 -0
  188. package/dist/todo/index.d.ts +2 -0
  189. package/dist/todo/index.d.ts.map +1 -0
  190. package/dist/todo/index.js +2 -0
  191. package/dist/todo/index.js.map +1 -0
  192. package/dist/todo/server.d.ts +2 -0
  193. package/dist/todo/server.d.ts.map +1 -0
  194. package/dist/todo/server.js +32 -0
  195. package/dist/todo/server.js.map +1 -0
  196. package/dist/types/index.d.ts +412 -0
  197. package/dist/types/index.d.ts.map +1 -0
  198. package/dist/types/index.js +5 -0
  199. package/dist/types/index.js.map +1 -0
  200. 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