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