agent-enderun 0.8.8 → 0.9.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 (103) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +4 -4
  2. package/{ENDERUN.md → .enderun/ENDERUN.md} +4 -2
  3. package/.enderun/PROJECT_MEMORY.md +12 -45
  4. package/.enderun/STATUS.md +9 -8
  5. package/.enderun/agents/agent_army_schema.json +29 -7
  6. package/.enderun/agents/analyst.md +3 -5
  7. package/.enderun/agents/backend.md +11 -31
  8. package/.enderun/agents/devops.md +2 -3
  9. package/.enderun/agents/explorer.md +4 -4
  10. package/.enderun/agents/frontend.md +76 -69
  11. package/.enderun/agents/git.md +3 -3
  12. package/.enderun/agents/manager.md +30 -42
  13. package/.enderun/agents/mobile.md +3 -3
  14. package/.enderun/agents/native.md +106 -0
  15. package/.enderun/agents/orchestrator.md +2 -2
  16. package/.enderun/agents/qa.md +7 -10
  17. package/.enderun/agents/schema/agent-lifecycle-schema.json +29 -8
  18. package/.enderun/agents/security.md +2 -2
  19. package/.enderun/cli-commands.json +4 -0
  20. package/.enderun/config.json +2 -2
  21. package/.enderun/knowledge/SHIM_TEMPLATE.md +25 -0
  22. package/.enderun/knowledge/eslint-standards.md +63 -0
  23. package/.enderun/knowledge/hermes_protocol.md +1 -1
  24. package/.enderun/knowledge/manager_authority_audit_enforcement.md +2 -2
  25. package/.enderun/mcp_config.json +9 -0
  26. package/.enderun/memory-graph/agent-contexts/analyst.json +1 -1
  27. package/.enderun/memory-graph/agent-contexts/backend.json +1 -1
  28. package/.enderun/memory-graph/agent-contexts/devops.json +1 -1
  29. package/.enderun/memory-graph/agent-contexts/explorer.json +1 -1
  30. package/.enderun/memory-graph/agent-contexts/frontend.json +1 -1
  31. package/.enderun/memory-graph/agent-contexts/git.json +1 -1
  32. package/.enderun/memory-graph/agent-contexts/manager.json +1 -1
  33. package/.enderun/memory-graph/agent-contexts/mobile.json +1 -1
  34. package/.enderun/memory-graph/agent-contexts/native.json +1 -0
  35. package/.enderun/memory-graph/agent-contexts/orchestrator.json +1 -1
  36. package/.enderun/memory-graph/agent-contexts/qa.json +1 -1
  37. package/.enderun/memory-graph/agent-contexts/security.json +1 -1
  38. package/.enderun/memory-graph/graph.json +123 -15
  39. package/.enderun/memory-graph/shared-facts.json +26 -31
  40. package/README.md +89 -413
  41. package/agent.md +25 -0
  42. package/bin/cli.js +9 -9
  43. package/bin/update-contract.js +34 -37
  44. package/bin/validate-agent-army.js +38 -13
  45. package/claude.md +14 -8
  46. package/docs/README.md +35 -16
  47. package/docs/tech-stack.md +30 -0
  48. package/eslint.config.js +68 -0
  49. package/framework-mcp/dist/index.js +184 -24
  50. package/framework-mcp/package.json +6 -33
  51. package/framework-mcp/tsconfig.json +3 -6
  52. package/gemini.md +12 -6
  53. package/grok.md +25 -0
  54. package/package.json +12 -6
  55. package/panda.config.ts +86 -86
  56. package/tsconfig.json +9 -0
  57. package/.enderun/logs/analyst.json +0 -1
  58. package/.enderun/logs/backend.json +0 -1
  59. package/.enderun/logs/devops.json +0 -1
  60. package/.enderun/logs/explorer.json +0 -1
  61. package/.enderun/logs/frontend.json +0 -1
  62. package/.enderun/logs/git.json +0 -1
  63. package/.enderun/logs/manager.json +0 -1
  64. package/.enderun/logs/mobile.json +0 -1
  65. package/.enderun/logs/native.json +0 -1
  66. package/.enderun/logs/orchestrator.json +0 -1
  67. package/.enderun/logs/qa.json +0 -1
  68. package/.enderun/logs/security.json +0 -1
  69. package/.env.example +0 -10
  70. package/cursor.md +0 -19
  71. package/framework-mcp/README.md +0 -55
  72. package/framework-mcp/dist/schemas.js +0 -84
  73. package/framework-mcp/dist/tools/codebase.js +0 -294
  74. package/framework-mcp/dist/tools/contract.js +0 -197
  75. package/framework-mcp/dist/tools/framework.js +0 -225
  76. package/framework-mcp/dist/tools/git.js +0 -51
  77. package/framework-mcp/dist/tools/governance.js +0 -696
  78. package/framework-mcp/dist/tools/index.js +0 -47
  79. package/framework-mcp/dist/tools/knowledge.js +0 -133
  80. package/framework-mcp/dist/tools/memory.js +0 -217
  81. package/framework-mcp/dist/tools/messages.js +0 -193
  82. package/framework-mcp/dist/tools/monitoring.js +0 -294
  83. package/framework-mcp/dist/tools/orchestration.js +0 -369
  84. package/framework-mcp/dist/tools/pipeline.js +0 -267
  85. package/framework-mcp/dist/tools/security.js +0 -137
  86. package/framework-mcp/dist/utils.js +0 -121
  87. package/framework-mcp/src/index.ts +0 -49
  88. package/framework-mcp/src/schemas.ts +0 -106
  89. package/framework-mcp/src/tools/codebase.ts +0 -284
  90. package/framework-mcp/src/tools/contract.ts +0 -221
  91. package/framework-mcp/src/tools/framework.ts +0 -223
  92. package/framework-mcp/src/tools/git.ts +0 -42
  93. package/framework-mcp/src/tools/governance.ts +0 -891
  94. package/framework-mcp/src/tools/index.ts +0 -50
  95. package/framework-mcp/src/tools/knowledge.ts +0 -141
  96. package/framework-mcp/src/tools/memory.ts +0 -207
  97. package/framework-mcp/src/tools/messages.ts +0 -213
  98. package/framework-mcp/src/tools/monitoring.ts +0 -351
  99. package/framework-mcp/src/tools/orchestration.ts +0 -440
  100. package/framework-mcp/src/tools/pipeline.ts +0 -353
  101. package/framework-mcp/src/tools/security.ts +0 -143
  102. package/framework-mcp/src/utils.ts +0 -130
  103. package/src/cli/index.ts +0 -2188
@@ -1,891 +0,0 @@
1
- /**
2
- * Governance Tools - MCP Tools for enforcing corporate governance standards
3
- *
4
- * These tools bridge the .enderun/knowledge/corporate-governance/ knowledge base
5
- * with actual code-level compliance verification.
6
- *
7
- * Philosophy: We do not just document standards — we actively verify them.
8
- *
9
- * All tools now return a standardized GovernanceToolResult format for consistency.
10
- */
11
-
12
- import fs from "fs";
13
- import path from "path";
14
- import {
15
- Project,
16
- SourceFile,
17
- SyntaxKind,
18
- FunctionDeclaration,
19
- MethodDeclaration,
20
- ArrowFunction,
21
- Node,
22
- InterfaceDeclaration,
23
- ParameterDeclaration
24
- } from "ts-morph";
25
-
26
- // --- Standard Types for all Governance Tools ---
27
-
28
- export interface GovernanceViolation {
29
- file: string;
30
- line?: number;
31
- rule: string;
32
- severity: "critical" | "high" | "medium" | "low";
33
- message: string;
34
- suggestion?: string;
35
- }
36
-
37
- export interface GovernanceReport {
38
- compliance_score: number; // 0-100
39
- total_checks: number;
40
- violations: GovernanceViolation[];
41
- passed_checks: number;
42
- summary: string;
43
- recommendations: string[];
44
- }
45
-
46
- /**
47
- * Standard, consistent output format for all Governance Tools.
48
- * This structure is designed to be both human-readable and machine-friendly.
49
- */
50
- export interface GovernanceToolResult {
51
- tool: string;
52
- success: boolean;
53
- executed_at: string;
54
- summary: string; // Kısa, insan dostu özet
55
- score?: number; // 0-100 arası uyum skoru (varsa)
56
- violations_count?: number;
57
- // Dynamic because governance tools return varying report shapes
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
- data: GovernanceReport | Record<string, any>;
60
- error?: string;
61
- }
62
-
63
- // --- Shared Helper Functions ---
64
-
65
- function createEmptyGovernanceReport(): GovernanceReport {
66
- return {
67
- compliance_score: 0,
68
- total_checks: 0,
69
- violations: [],
70
- passed_checks: 0,
71
- summary: "",
72
- recommendations: [],
73
- };
74
- }
75
-
76
- /**
77
- * Creates a standardized result object for Governance tools.
78
- */
79
- function createGovernanceToolResult(
80
- toolName: string,
81
- // Dynamic tool output shape
82
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
- data: GovernanceReport | Record<string, any>,
84
- summary: string,
85
- success = true,
86
- error?: string
87
- ): GovernanceToolResult {
88
- const base: GovernanceToolResult = {
89
- tool: toolName,
90
- success,
91
- executed_at: new Date().toISOString(),
92
- summary,
93
- data,
94
- error,
95
- };
96
-
97
- // Eğer data bir GovernanceReport ise, ekstra alanları doldur
98
- if (data && typeof data === "object" && "compliance_score" in data) {
99
- const report = data as GovernanceReport;
100
- base.score = report.compliance_score;
101
- base.violations_count = report.violations.length;
102
- }
103
-
104
- return base;
105
- }
106
-
107
- // --- Helper Functions ---
108
-
109
- function collectTypeFiles(dir: string): string[] {
110
- const files: string[] = [];
111
- if (!fs.existsSync(dir)) return files;
112
-
113
- const entries = fs.readdirSync(dir, { withFileTypes: true });
114
- for (const entry of entries) {
115
- const fullPath = path.join(dir, entry.name);
116
- if (entry.isDirectory()) {
117
- files.push(...collectTypeFiles(fullPath));
118
- } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
119
- files.push(fullPath);
120
- }
121
- }
122
- return files;
123
- }
124
-
125
- // --- Safe ts-morph helpers for mixed Function / Method / ArrowFunction nodes ---
126
- type FunctionLike = FunctionDeclaration | MethodDeclaration | ArrowFunction;
127
-
128
- function getFunctionLikeName(node: FunctionLike | Node): string {
129
- if (!node) return '<anonymous>';
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
- if (typeof (node as any).getName === 'function') {
132
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
- const n = (node as any).getName();
134
- if (n) return n;
135
- }
136
- const parent = node.getParent?.();
137
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
138
- if (parent && typeof (parent as any).getName === 'function') {
139
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
- const pn = (parent as any).getName();
141
- if (pn) return pn;
142
- }
143
- return '<anonymous>';
144
- }
145
-
146
- function getFunctionLikeParameters(node: FunctionLike | Node): ParameterDeclaration[] {
147
- if (!node) return [];
148
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
- if (typeof (node as any).getParameters === 'function') {
150
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
151
- return (node as any).getParameters();
152
- }
153
- return [];
154
- }
155
-
156
- function collectSourceFiles(dir: string): string[] {
157
- const files: string[] = [];
158
- if (!fs.existsSync(dir)) return files;
159
-
160
- const entries = fs.readdirSync(dir, { withFileTypes: true });
161
- for (const entry of entries) {
162
- const fullPath = path.join(dir, entry.name);
163
- if (entry.isDirectory()) {
164
- files.push(...collectSourceFiles(fullPath));
165
- } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
166
- files.push(fullPath);
167
- }
168
- }
169
- return files;
170
- }
171
-
172
- /**
173
- * Creates a standard ts-morph Project instance for analysis.
174
- */
175
- function createAnalysisProject(projectRoot: string): Project {
176
- return new Project({
177
- tsConfigFilePath: path.join(projectRoot, "tsconfig.json"),
178
- skipAddingFilesFromTsConfig: true,
179
- });
180
- }
181
-
182
- /**
183
- * Returns high-risk action patterns.
184
- *
185
- * Currently uses a stable, curated list derived from corporate-governance standards.
186
- * This approach is much more reliable than parsing markdown files with regex.
187
- *
188
- * In the future, this can be expanded to read from a structured JSON/YAML file
189
- * if more dynamic rule management is needed.
190
- */
191
- function loadHighRiskPatterns(_projectRoot: string): string[] {
192
- // Stable, well-defined high-risk keywords based on corporate governance standards
193
- // (high-risk-actions-managerapproval.md and related documents)
194
- return [
195
- // Role & Permission elevation
196
- "promote", "elevate", "superadmin", "admin", "role", "permission", "grant", "revoke",
197
-
198
- // Destructive operations
199
- "delete", "purge", "bulk",
200
-
201
- // System configuration & reset
202
- "reset", "configure", "config", "system",
203
-
204
- // Sensitive data operations
205
- "export", "audit"
206
- ];
207
- }
208
-
209
- /**
210
- * Determines if a function is likely performing a high-risk administrative action.
211
- * This is the central detection logic for verify_high_risk_compliance.
212
- */
213
- function isHighRiskFunction(functionName: string, bodyText: string, highRiskKeywords: string[]): boolean {
214
- const name = functionName.toLowerCase();
215
- const body = bodyText.toLowerCase();
216
- const combined = name + body;
217
-
218
- // 1. Temel yüksek riskli kelimeler
219
- const hasKeyword = highRiskKeywords.some(kw =>
220
- name.includes(kw) || body.includes(kw)
221
- );
222
-
223
- // 2. Route ve endpoint pattern'ları (REST + admin rotaları)
224
- const routePatterns = [
225
- /post.*role|put.*role|patch.*role|delete.*user/i,
226
- /post.*admin|put.*permission/i,
227
- /\/(api|admin|users?|roles?|permissions?)\/.*(role|admin|permission|promote|elevate|grant|revoke)/i
228
- ];
229
- const hasRoutePattern = routePatterns.some(regex => regex.test(combined));
230
-
231
- // 3. Semantic yetki değiştirme desenleri (kod içinde privilege manipülasyonu)
232
- const semanticPatterns = [
233
- /changeRole|assignRole|updateRole|setPermission|grantPermission|revokePermission|makeAdmin|setAdmin/i,
234
- /role\s*=\s*['"`]?(admin|superadmin|super_admin)/i,
235
- /permissions?\s*\.\s*(push|add)/i
236
- ];
237
- const hasSemanticPrivilegeChange = semanticPatterns.some(regex => regex.test(body));
238
-
239
- return hasKeyword || hasRoutePattern || hasSemanticPrivilegeChange;
240
- }
241
-
242
- function findInterface(sourceFiles: SourceFile[], interfaceName: string) {
243
- for (const sf of sourceFiles) {
244
- const iface = sf.getInterface(interfaceName);
245
- if (iface) return iface;
246
- }
247
- return undefined;
248
- }
249
-
250
- function getInterfaceProperties(iface: InterfaceDeclaration): string[] {
251
- return iface.getProperties().map((p) => p.getName());
252
- }
253
-
254
- // --- Tool Definitions ---
255
-
256
- export const governanceTools = [
257
- {
258
- name: "verify_high_risk_compliance",
259
- description: "Analyzes high-risk administrative endpoints and verifies compliance with corporate governance standards (managerApproval, branded types, audit logging, error handling).",
260
- inputSchema: {
261
- type: "object",
262
- properties: {
263
- backendPath: {
264
- type: "string",
265
- description: "Relative path to backend source (default: apps/backend/src)",
266
- },
267
- strictMode: {
268
- type: "boolean",
269
- description: "If true, treats warnings as violations (default: false)",
270
- },
271
- },
272
- },
273
- },
274
- {
275
- name: "detect_contract_drift",
276
- description: "Detects contract drift between backend types and frontend copies, with focus on corporate governance contracts (Branded Types, ManagerApproval, etc.).",
277
- inputSchema: {
278
- type: "object",
279
- properties: {
280
- backendTypesPath: {
281
- type: "string",
282
- description: "Path to backend types directory (default: apps/backend/src/types)",
283
- },
284
- frontendTypesPath: {
285
- type: "string",
286
- description: "Path to frontend type copies (default: apps/web/src/types)",
287
- },
288
- },
289
- },
290
- },
291
- {
292
- name: "validate_branded_types_usage",
293
- description: "Scans the codebase for proper usage of Branded Types as defined in corporate governance standards.",
294
- inputSchema: {
295
- type: "object",
296
- properties: {
297
- targetPath: {
298
- type: "string",
299
- description: "Directory to scan (default: apps/backend/src)",
300
- },
301
- },
302
- },
303
- },
304
- {
305
- name: "audit_governance_compliance",
306
- description: "Performs a high-level governance audit of the entire project by combining results from other governance tools. Returns an overall compliance score and prioritized issues.",
307
- inputSchema: {
308
- type: "object",
309
- properties: {
310
- backendPath: {
311
- type: "string",
312
- description: "Path to backend source (default: apps/backend/src)",
313
- },
314
- includeContractCheck: {
315
- type: "boolean",
316
- description: "Whether to also run contract drift detection (default: true)",
317
- },
318
- },
319
- },
320
- },
321
- ];
322
-
323
- // --- Handlers (Implementation starts here) ---
324
-
325
- // args is any because these are dynamic MCP tool handlers
326
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
- export const governanceHandlers: Record<string, (args: any, _projectRoot: string) => Promise<{ content: Array<{ type: string; text: string }> }>> = {
328
-
329
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
330
- verify_high_risk_compliance: async (args: any, projectRoot: string) => {
331
- const backendPath = args.backendPath || "apps/backend/src";
332
- const fullBackendPath = path.join(projectRoot, backendPath);
333
-
334
- if (!fs.existsSync(fullBackendPath)) {
335
- const result = createGovernanceToolResult(
336
- "verify_high_risk_compliance",
337
- createEmptyGovernanceReport(),
338
- `Backend source directory not found: ${backendPath}`,
339
- false
340
- );
341
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
342
- }
343
-
344
- const project = createAnalysisProject(projectRoot);
345
- const sourceFiles = collectSourceFiles(fullBackendPath).map(f =>
346
- project.addSourceFileAtPath(f)
347
- );
348
-
349
- const report = createEmptyGovernanceReport();
350
-
351
- // Load high-risk patterns dynamically from corporate governance knowledge
352
- const highRiskKeywords = loadHighRiskPatterns(projectRoot);
353
-
354
- let highRiskFunctionsFound = 0;
355
-
356
- for (const sourceFile of sourceFiles) {
357
- // Daha kapsamlı fonksiyon + method + route handler tespiti
358
- const functions = [
359
- ...sourceFile.getFunctions(),
360
- ...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
361
- ...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
362
- ];
363
-
364
- for (const func of functions) {
365
- const name = getFunctionLikeName(func);
366
- const bodyText = func.getBodyText() || "";
367
-
368
- // === GÜÇLENDİRİLMİŞ HIGH-RISK FONKSİYON TESPİTİ ===
369
- const isLikelyHighRisk = isHighRiskFunction(name, bodyText, highRiskKeywords);
370
-
371
- if (!isLikelyHighRisk) continue;
372
-
373
- highRiskFunctionsFound++;
374
-
375
- // === Daha Derin ve Yapısal AST Tabanlı Kontroller ===
376
-
377
- const parameters = getFunctionLikeParameters(func);
378
-
379
- // 1. High-risk validation kontrolü (daha güçlü AST + semantic)
380
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
- const hasProperHighRiskCheck = func.getDescendantsOfKind(SyntaxKind.IfStatement).some((ifStmt: any) => {
382
- const conditionText = ifStmt.getExpression().getText().toLowerCase();
383
- const hasFlag = conditionText.includes("ishighriskadminaction");
384
- const hasApproval = conditionText.includes("managerapproval");
385
- return hasFlag && hasApproval;
386
- });
387
-
388
- if (!hasProperHighRiskCheck) {
389
- report.violations.push({
390
- file: path.relative(projectRoot, sourceFile.getFilePath()),
391
- line: func.getStartLineNumber(),
392
- rule: "insufficient_high_risk_validation",
393
- severity: "critical",
394
- message: `High-risk function "${name}" does not contain proper conditional check for both isHighRiskAdminAction and managerApproval.`,
395
- suggestion: "Add: if (!body.isHighRiskAdminAction || !body.managerApproval) { throw new HighRiskActionRequiresApprovalError(...) }"
396
- });
397
- } else {
398
- // Ekstra: ManagerApproval yapısal validasyonu (approvedBy kontrolü)
399
- const hasApprovedByCheck = /approvedBy|approval\.approvedBy/i.test(bodyText);
400
- if (!hasApprovedByCheck) {
401
- report.violations.push({
402
- file: path.relative(projectRoot, sourceFile.getFilePath()),
403
- line: func.getStartLineNumber(),
404
- rule: "weak_manager_approval_validation",
405
- severity: "high",
406
- message: `High-risk function "${name}" checks managerApproval but does not validate approvedBy field.`,
407
- suggestion: "Add check: if (managerApproval.approvedBy !== 'manager') { ... }"
408
- });
409
- }
410
- }
411
-
412
- // 2. Typed Error kullanımı (daha doğru tespit)
413
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
414
- const throwsTypedError = func.getDescendantsOfKind(SyntaxKind.ThrowStatement).some((throwStmt: any) => {
415
- const expr = throwStmt.getExpression().getText();
416
- return expr.includes("HighRiskActionRequiresApprovalError") || expr.includes("DomainError");
417
- });
418
-
419
- if (!throwsTypedError && hasProperHighRiskCheck) {
420
- report.violations.push({
421
- file: path.relative(projectRoot, sourceFile.getFilePath()),
422
- line: func.getStartLineNumber(),
423
- rule: "missing_typed_error",
424
- severity: "high",
425
- message: `High-risk function "${name}" performs governance checks but does not throw a typed DomainError.`,
426
- suggestion: "Throw HighRiskActionRequiresApprovalError for high-risk governance violations."
427
- });
428
- }
429
-
430
- // 3. Audit Logging kontrolü (daha iyi tespit)
431
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
432
- const hasAuditLogging = func.getDescendantsOfKind(SyntaxKind.CallExpression).some((call: any) => {
433
- const callText = call.getExpression().getText().toLowerCase();
434
- return callText.includes("audit") || callText.includes("log");
435
- }) || /auditLog|audit\.create/i.test(bodyText);
436
-
437
- if (!hasAuditLogging) {
438
- report.violations.push({
439
- file: path.relative(projectRoot, sourceFile.getFilePath()),
440
- line: func.getStartLineNumber(),
441
- rule: "missing_audit_logging",
442
- severity: "high",
443
- message: `High-risk function "${name}" does not create an audit log entry.`,
444
- suggestion: "Always create an audit log entry for high-risk actions (see audit-logging-standard.md)."
445
- });
446
- }
447
-
448
- // 4. Branded Types kullanımı (parametre seviyesinde gerçek tip kontrolü)
449
- let hasBrandedTypesInParams = false;
450
- for (const param of parameters) {
451
- const typeText = param.getType().getText();
452
- if (/\b(UserID|RoleID|ApprovalID|TraceID)\b/.test(typeText)) {
453
- hasBrandedTypesInParams = true;
454
- break;
455
- }
456
- }
457
-
458
- if (!hasBrandedTypesInParams) {
459
- report.violations.push({
460
- file: path.relative(projectRoot, sourceFile.getFilePath()),
461
- line: func.getStartLineNumber(),
462
- rule: "missing_branded_types",
463
- severity: "high",
464
- message: `High-risk function "${name}" does not use Branded Types in its parameters.`,
465
- suggestion: "Use UserID, RoleID, ApprovalID etc. instead of plain string or number."
466
- });
467
- }
468
- }
469
- }
470
-
471
- // Her high-risk fonksiyon için şu kontroller yapılıyor:
472
- // 1. High-risk flag + managerApproval kontrolü
473
- // 2. Typed DomainError (HighRiskActionRequiresApprovalError) kullanımı
474
- // 3. Audit logging çağrısı
475
- // 4. Branded Types kullanımı (parametrelerde)
476
- const totalChecks = highRiskFunctionsFound * 4;
477
- const violationsCount = report.violations.length;
478
- const passed = Math.max(0, totalChecks - violationsCount);
479
-
480
- report.total_checks = totalChecks;
481
- report.passed_checks = passed;
482
- report.compliance_score = totalChecks > 0
483
- ? Math.round((passed / totalChecks) * 100)
484
- : 100;
485
-
486
- if (violationsCount === 0 && highRiskFunctionsFound > 0) {
487
- report.summary = `✅ Good compliance. Analyzed ${highRiskFunctionsFound} high-risk functions.`;
488
- } else if (highRiskFunctionsFound === 0) {
489
- report.summary = "No high-risk functions matching governance patterns were detected.";
490
- } else {
491
- report.summary = `Analyzed ${highRiskFunctionsFound} high-risk functions. Found ${violationsCount} governance violations.`;
492
- }
493
-
494
- if (violationsCount > 0) {
495
- report.recommendations.push("Review all critical violations first (missing managerApproval checks).");
496
- report.recommendations.push("Load corporate-governance/high-risk-actions-managerapproval.md for the exact expected pattern.");
497
- }
498
-
499
- const finalResult = createGovernanceToolResult(
500
- "verify_high_risk_compliance",
501
- report,
502
- report.summary || "High-risk compliance analysis completed."
503
- );
504
- return {
505
- content: [{
506
- type: "text",
507
- text: JSON.stringify(finalResult, null, 2)
508
- }]
509
- };
510
- },
511
-
512
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
513
- detect_contract_drift: async (args: any, projectRoot: string) => {
514
- const backendTypesPath = args.backendTypesPath || "apps/backend/src/types";
515
- const frontendTypesPath = args.frontendTypesPath || "apps/web/src/types";
516
-
517
- const backendDir = path.join(projectRoot, backendTypesPath);
518
- const frontendDir = path.join(projectRoot, frontendTypesPath);
519
-
520
- if (!fs.existsSync(backendDir)) {
521
- const result = createGovernanceToolResult(
522
- "detect_contract_drift",
523
- { drift_detected: false },
524
- `Backend types directory not found: ${backendTypesPath}`,
525
- false
526
- );
527
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
528
- }
529
-
530
- const project = createAnalysisProject(projectRoot);
531
-
532
- // Load backend types
533
- const backendFiles = collectTypeFiles(backendDir);
534
- const backendSourceFiles = backendFiles.map(f => project.addSourceFileAtPath(f));
535
-
536
- // Load frontend type copies (if exists)
537
- let frontendSourceFiles: SourceFile[] = [];
538
- if (fs.existsSync(frontendDir)) {
539
- const frontendFiles = collectTypeFiles(frontendDir);
540
- frontendSourceFiles = frontendFiles.map(f => project.addSourceFileAtPath(f));
541
- }
542
-
543
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
544
- const drifts: any[] = [];
545
-
546
- // Compare interfaces (focus on governance-related types)
547
- const governanceTypeNames = [
548
- "UserID", "RoleID", "ApprovalID", "TraceID",
549
- "ManagerApproval", "HighRiskActionPayload",
550
- "User", "Role", "DomainError"
551
- ];
552
-
553
- for (const typeName of governanceTypeNames) {
554
- const backendInterface = findInterface(backendSourceFiles, typeName);
555
- const frontendInterface = findInterface(frontendSourceFiles, typeName);
556
-
557
- if (backendInterface && !frontendInterface) {
558
- drifts.push({
559
- type: typeName,
560
- issue: "MISSING_IN_FRONTEND",
561
- severity: "high",
562
- message: `Governance type "${typeName}" exists in backend but is missing in frontend copies.`,
563
- });
564
- }
565
-
566
- if (backendInterface && frontendInterface) {
567
- const backendProps = getInterfaceProperties(backendInterface);
568
- const frontendProps = getInterfaceProperties(frontendInterface);
569
-
570
- const missingInFrontend = backendProps.filter(p => !frontendProps.includes(p));
571
- const extraInFrontend = frontendProps.filter(p => !backendProps.includes(p));
572
-
573
- if (missingInFrontend.length > 0 || extraInFrontend.length > 0) {
574
- drifts.push({
575
- type: typeName,
576
- issue: "STRUCTURE_MISMATCH",
577
- severity: "high",
578
- missing_in_frontend: missingInFrontend,
579
- extra_in_frontend: extraInFrontend,
580
- message: `Structural drift detected in governance type "${typeName}".`,
581
- });
582
- }
583
- }
584
- }
585
-
586
- const report = {
587
- drift_detected: drifts.length > 0,
588
- total_governance_types_checked: governanceTypeNames.length,
589
- drifts,
590
- backend_types_path: backendTypesPath,
591
- frontend_types_path: frontendTypesPath,
592
- recommendation: drifts.length > 0
593
- ? "Run update_contract_hash after aligning frontend copies. Consider using corporate-governance-playbook.md for alignment process."
594
- : "No significant drift detected in core governance types.",
595
- };
596
-
597
- const summary = drifts.length > 0
598
- ? `Contract drift detected in ${drifts.length} governance types.`
599
- : "No significant contract drift found in core governance types.";
600
-
601
- const finalResult = createGovernanceToolResult("detect_contract_drift", report, summary);
602
- return {
603
- content: [{
604
- type: "text",
605
- text: JSON.stringify(finalResult, null, 2)
606
- }]
607
- };
608
- },
609
-
610
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
611
- validate_branded_types_usage: async (args: any, projectRoot: string) => {
612
- const targetPath = args.targetPath || "apps/backend/src";
613
-
614
- const fullPath = path.join(projectRoot, targetPath);
615
-
616
- interface BrandedTypeReport {
617
- total_files_scanned: number;
618
- branded_types_found: number;
619
- plain_id_violations: Array<{
620
- file: string;
621
- line: number;
622
- issue: string;
623
- message: string;
624
- }>;
625
- summary: string;
626
- recommendations: string[];
627
- }
628
-
629
- const report: BrandedTypeReport = {
630
- total_files_scanned: 0,
631
- branded_types_found: 0,
632
- plain_id_violations: [],
633
- summary: "",
634
- recommendations: [],
635
- };
636
-
637
- if (!fs.existsSync(fullPath)) {
638
- const result = createGovernanceToolResult(
639
- "validate_branded_types_usage",
640
- { total_files_scanned: 0, plain_id_violations: [] },
641
- `Target directory not found: ${targetPath}`,
642
- false
643
- );
644
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
645
- }
646
-
647
- const project = createAnalysisProject(projectRoot);
648
-
649
- const sourceFiles = collectSourceFiles(fullPath).map(f =>
650
- project.addSourceFileAtPath(f)
651
- );
652
-
653
- report.total_files_scanned = sourceFiles.length;
654
-
655
- // Branded type names we care about (from our governance standards)
656
- const brandedTypeNames = ["UserID", "RoleID", "ApprovalID", "TraceID"];
657
-
658
- for (const sourceFile of sourceFiles) {
659
- // Check interface/type definitions first
660
- const interfaces = sourceFile.getInterfaces();
661
- for (const iface of interfaces) {
662
- for (const prop of iface.getProperties()) {
663
- const typeText = prop.getType().getText();
664
- const propName = prop.getName().toLowerCase();
665
-
666
- // Look for ID fields that should be branded
667
- if (propName.includes("id") || propName.includes("userid") || propName.includes("roleid")) {
668
- const isBranded = brandedTypeNames.some(bt => typeText.includes(bt));
669
- if (!isBranded && (typeText.includes("string") || typeText.includes("number"))) {
670
- report.plain_id_violations.push({
671
- file: path.relative(projectRoot, sourceFile.getFilePath()),
672
- line: prop.getStartLineNumber(),
673
- issue: "plain_id_in_interface",
674
- message: `Property "${prop.getName()}" uses plain ${typeText} instead of a branded type.`,
675
- });
676
- }
677
- }
678
- }
679
- }
680
-
681
- // Check function parameters and variables
682
- const functions = [
683
- ...sourceFile.getFunctions(),
684
- ...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
685
- ];
686
-
687
- for (const func of functions) {
688
- // Parameters
689
- for (const param of getFunctionLikeParameters(func)) {
690
- const typeText = param.getType().getText();
691
- const paramName = param.getName().toLowerCase();
692
-
693
- if (paramName.includes("id") || paramName.includes("userid") || paramName.includes("roleid")) {
694
- const isBranded = brandedTypeNames.some(bt => typeText.includes(bt));
695
- if (!isBranded && (typeText.includes("string") || typeText.includes("number"))) {
696
- report.plain_id_violations.push({
697
- file: path.relative(projectRoot, sourceFile.getFilePath()),
698
- line: param.getStartLineNumber(),
699
- issue: "plain_id_in_parameter",
700
- message: `Parameter "${param.getName()}" uses plain ${typeText} instead of branded type (e.g. UserID).`,
701
- });
702
- } else if (isBranded) {
703
- report.branded_types_found++;
704
- }
705
- }
706
- }
707
- }
708
- }
709
-
710
- const violationCount = report.plain_id_violations.length;
711
-
712
- if (violationCount === 0) {
713
- report.summary = `✅ Good. No plain ID usages found in governance-related contexts across ${report.total_files_scanned} files.`;
714
- } else {
715
- report.summary = `Found ${violationCount} violations where plain string/number was used instead of branded types.`;
716
- report.recommendations.push("Replace plain IDs with branded types (UserID, RoleID, ApprovalID, etc.).");
717
- report.recommendations.push("See branded-types-law.md for the full standard.");
718
- }
719
-
720
- const summary = violationCount === 0
721
- ? `No branded type violations found in ${report.total_files_scanned} files.`
722
- : `${violationCount} plain ID violations detected.`;
723
-
724
- const finalResult = createGovernanceToolResult("validate_branded_types_usage", report, summary);
725
- return {
726
- content: [{
727
- type: "text",
728
- text: JSON.stringify(finalResult, null, 2)
729
- }]
730
- };
731
- },
732
-
733
- // ============================================
734
- // audit_governance_compliance (Bağımsız Versiyon)
735
- // ============================================
736
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
737
- audit_governance_compliance: async (args: any, projectRoot: string) => {
738
- const backendPath = args.backendPath || "apps/backend/src";
739
- const includeContractCheck = args.includeContractCheck ?? true;
740
-
741
- const fullBackendPath = path.join(projectRoot, backendPath);
742
- const frontendTypesPath = path.join(projectRoot, "apps/web/src/types");
743
-
744
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
745
- const overview: any = {
746
- overall_score: 0,
747
- areas: {},
748
- critical_issues: [],
749
- summary: "",
750
- recommendations: [],
751
- };
752
-
753
- // ============================================
754
- // 1. High-Risk Compliance (Bağımsız basit analiz)
755
- // ============================================
756
- let highRiskViolations = 0;
757
- if (fs.existsSync(fullBackendPath)) {
758
- const project = createAnalysisProject(projectRoot);
759
- const sourceFiles = collectSourceFiles(fullBackendPath).map(f => project.addSourceFileAtPath(f));
760
-
761
- const highRiskKeywords = ["promote", "elevate", "superadmin", "admin", "role", "delete", "purge", "reset"];
762
-
763
- for (const sourceFile of sourceFiles) {
764
- const functions = [
765
- ...sourceFile.getFunctions(),
766
- ...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
767
- ...sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction),
768
- ];
769
-
770
- for (const func of functions) {
771
- const name = getFunctionLikeName(func);
772
- const bodyText = func.getBodyText() || "";
773
- const lowerBody = bodyText.toLowerCase();
774
-
775
- const isHighRisk = highRiskKeywords.some(kw => name.toLowerCase().includes(kw) || lowerBody.includes(kw));
776
-
777
- if (isHighRisk) {
778
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
779
- const hasFlagAndApproval = func.getDescendantsOfKind(SyntaxKind.IfStatement).some((ifStmt: any) => {
780
- const cond = ifStmt.getExpression().getText().toLowerCase();
781
- return cond.includes("ishighriskadminaction") && cond.includes("managerapproval");
782
- });
783
-
784
- if (!hasFlagAndApproval) highRiskViolations++;
785
- }
786
- }
787
- }
788
- }
789
-
790
- overview.areas.high_risk = {
791
- violations: highRiskViolations,
792
- status: highRiskViolations === 0 ? "good" : "issues_found",
793
- };
794
-
795
- // ============================================
796
- // 2. Branded Types (Bağımsız basit analiz)
797
- // ============================================
798
- let brandedViolations = 0;
799
- if (fs.existsSync(fullBackendPath)) {
800
- const project = createAnalysisProject(projectRoot);
801
- const sourceFiles = collectSourceFiles(fullBackendPath).map(f => project.addSourceFileAtPath(f));
802
- const brandedNames = ["UserID", "RoleID", "ApprovalID", "TraceID"];
803
-
804
- for (const sourceFile of sourceFiles) {
805
- const functions = [
806
- ...sourceFile.getFunctions(),
807
- ...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
808
- ];
809
-
810
- for (const func of functions) {
811
- const params = func.getParameters();
812
- for (const param of params) {
813
- const typeText = param.getType().getText();
814
- const paramName = param.getName().toLowerCase();
815
-
816
- if ((paramName.includes("id") || paramName.includes("userid") || paramName.includes("roleid")) &&
817
- !brandedNames.some(b => typeText.includes(b)) &&
818
- (typeText.includes("string") || typeText.includes("number"))) {
819
- brandedViolations++;
820
- }
821
- }
822
- }
823
- }
824
- }
825
-
826
- overview.areas.branded_types = {
827
- violations: brandedViolations,
828
- status: brandedViolations === 0 ? "good" : "issues_found",
829
- };
830
-
831
- // ============================================
832
- // 3. Contract Drift (basit klasör karşılaştırması)
833
- // ============================================
834
- if (includeContractCheck) {
835
- const backendTypesExist = fs.existsSync(path.join(projectRoot, "apps/backend/src/types"));
836
- const frontendTypesExist = fs.existsSync(frontendTypesPath);
837
-
838
- overview.areas.contract_drift = {
839
- backend_types_present: backendTypesExist,
840
- frontend_types_present: frontendTypesExist,
841
- likely_drift: backendTypesExist && !frontendTypesExist,
842
- };
843
- }
844
-
845
- // ============================================
846
- // Genel Skor Hesaplama
847
- // ============================================
848
- const highRiskScore = overview.areas.high_risk.violations === 0 ? 85 : 55;
849
- const brandedScore = overview.areas.branded_types.violations === 0 ? 90 : 60;
850
- const contractScore = overview.areas.contract_drift?.likely_drift === false ? 80 : 50;
851
-
852
- const scores = [highRiskScore, brandedScore, contractScore].filter(Boolean);
853
- overview.overall_score = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
854
-
855
- // Kritik sorunları topla
856
- if (overview.areas.high_risk.violations > 0) {
857
- overview.critical_issues.push({
858
- area: "high_risk",
859
- count: overview.areas.high_risk.violations,
860
- message: "High-risk functions without proper managerApproval validation detected.",
861
- });
862
- }
863
-
864
- if (overview.areas.branded_types.violations > 0) {
865
- overview.critical_issues.push({
866
- area: "branded_types",
867
- count: overview.areas.branded_types.violations,
868
- message: "Plain string/number IDs used instead of Branded Types.",
869
- });
870
- }
871
-
872
- overview.summary = `Overall governance compliance score: ${overview.overall_score}/100 (Independent analysis)`;
873
-
874
- if (overview.overall_score < 70) {
875
- overview.recommendations.push("Focus on high-risk validation and Branded Types first.");
876
- }
877
-
878
- const finalResult = createGovernanceToolResult(
879
- "audit_governance_compliance",
880
- overview,
881
- overview.summary
882
- );
883
-
884
- return {
885
- content: [{
886
- type: "text",
887
- text: JSON.stringify(finalResult, null, 2)
888
- }]
889
- };
890
- },
891
- };