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,284 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { Project } from "ts-morph";
4
- import {
5
- collectFilesRecursively,
6
- resolveSafePath
7
- } from "../utils.js";
8
- import {
9
- SEARCH_CODEBASE_ARGS_SCHEMA,
10
- ANALYZE_DEPENDENCIES_ARGS_SCHEMA,
11
- ANALYZE_CODEBASE_INTELLIGENCE_ARGS_SCHEMA,
12
- ANALYZE_PROCEDURAL_CONTINUITY_ARGS_SCHEMA,
13
- GENERATE_DEPENDENCY_GRAPH_ARGS_SCHEMA
14
- } from "../schemas.js";
15
-
16
- export const codebaseTools = [
17
- {
18
- name: "search_codebase",
19
- description: "Semantic search across the codebase using grep for exact matches and context. Ideal for finding logic and patterns.",
20
- inputSchema: {
21
- type: "object",
22
- properties: {
23
- query: {
24
- type: "string",
25
- description: "Search query or regex pattern",
26
- },
27
- extension: {
28
- type: "string",
29
- description: "File extension filter (e.g., ts, md)",
30
- default: "ts",
31
- },
32
- },
33
- required: ["query"],
34
- },
35
- },
36
- {
37
- name: "codebase_search",
38
- description: "Compatibility alias for search_codebase. Use when older agent prompts still reference codebase_search.",
39
- inputSchema: {
40
- type: "object",
41
- properties: {
42
- query: {
43
- type: "string",
44
- description: "Search query or regex pattern",
45
- },
46
- extension: {
47
- type: "string",
48
- description: "File extension filter (e.g., ts, md)",
49
- default: "ts",
50
- },
51
- },
52
- required: ["query"],
53
- },
54
- },
55
- {
56
- name: "analyze_dependencies",
57
- description: "Analyze code dependencies for a specific file or folder using import tracking.",
58
- inputSchema: {
59
- type: "object",
60
- properties: {
61
- path: {
62
- type: "string",
63
- description: "Path to analyze (relative to project root)",
64
- },
65
- },
66
- required: ["path"],
67
- },
68
- },
69
- {
70
- name: "codebase_graph_query",
71
- description: "Compatibility alias for analyze_dependencies. Returns import-level dependency information for a file or folder.",
72
- inputSchema: {
73
- type: "object",
74
- properties: {
75
- path: {
76
- type: "string",
77
- description: "Path to analyze (relative to project root)",
78
- },
79
- },
80
- required: ["path"],
81
- },
82
- },
83
- {
84
- name: "analyze_codebase_intelligence",
85
- description: "Scans the codebase for complexity spikes and potential dead code (exported but unused symbols).",
86
- inputSchema: {
87
- type: "object",
88
- properties: {
89
- path: {
90
- type: "string",
91
- description: "Path to scan (relative to project root)",
92
- default: ".",
93
- },
94
- },
95
- },
96
- },
97
- {
98
- name: "analyze_procedural_continuity",
99
- description: "Compares a target file with a reference file to identify stylistic or structural deviations. Ensures agents follow existing patterns.",
100
- inputSchema: {
101
- type: "object",
102
- properties: {
103
- targetPath: {
104
- type: "string",
105
- description: "Path to the newly created/modified file",
106
- },
107
- referencePath: {
108
- type: "string",
109
- description: "Path to an existing 'gold standard' file for comparison",
110
- },
111
- },
112
- required: ["targetPath", "referencePath"],
113
- },
114
- },
115
- {
116
- name: "generate_dependency_graph",
117
- description: "Generates a Mermaid-compatible dependency graph of the codebase or a specific directory.",
118
- inputSchema: {
119
- type: "object",
120
- properties: {
121
- path: {
122
- type: "string",
123
- description: "Path to scan (relative to project root)",
124
- default: "src",
125
- },
126
- format: {
127
- type: "string",
128
- enum: ["mermaid", "json"],
129
- default: "mermaid",
130
- },
131
- },
132
- },
133
- },
134
- ];
135
-
136
- export const codebaseHandlers = {
137
- search_codebase: async (args: unknown, projectRoot: string) => {
138
- const parsed = SEARCH_CODEBASE_ARGS_SCHEMA.safeParse(args);
139
- if (!parsed.success) {
140
- return { content: [{ type: "text", text: "Invalid query/extension argument." }] };
141
- }
142
- const { query, extension } = parsed.data;
143
- try {
144
- const files = collectFilesRecursively(projectRoot, new Set([extension]));
145
- let queryRegex: RegExp;
146
- try {
147
- queryRegex = new RegExp(query, "i");
148
- } catch {
149
- return { content: [{ type: "text", text: "Invalid regex pattern in query." }] };
150
- }
151
- const matches: string[] = [];
152
- const MAX_RESULTS = 30;
153
- for (const filePath of files) {
154
- if (matches.length >= MAX_RESULTS) break;
155
- const content = fs.readFileSync(filePath, "utf-8");
156
- const lines = content.split("\n");
157
- for (let i = 0; i < lines.length; i++) {
158
- if (matches.length >= MAX_RESULTS) break;
159
- const line = lines[i];
160
- if (queryRegex.test(line)) {
161
- const relativePath = path.relative(projectRoot, filePath);
162
- matches.push(`- ${relativePath}:${i + 1}: ${line.trim()}`);
163
- }
164
- }
165
- }
166
- return { content: [{ type: "text", text: matches.join("\n") || "No matches found." }] };
167
- } catch {
168
- return { content: [{ type: "text", text: "Search failed." }] };
169
- }
170
- },
171
- analyze_dependencies: async (args: unknown, projectRoot: string) => {
172
- const parsed = ANALYZE_DEPENDENCIES_ARGS_SCHEMA.safeParse(args);
173
- if (!parsed.success) {
174
- return { content: [{ type: "text", text: "Invalid path argument." }] };
175
- }
176
- const targetPath = parsed.data.path;
177
- try {
178
- const fullPath = resolveSafePath(projectRoot, targetPath);
179
- if (!fs.existsSync(fullPath)) return { content: [{ type: "text", text: `Path not found: ${targetPath}` }] };
180
- const stats = fs.statSync(fullPath);
181
- const tsProject = new Project({ compilerOptions: { allowJs: true } });
182
- if (stats.isDirectory()) {
183
- tsProject.addSourceFilesAtPaths(path.join(fullPath, "**/*.{ts,tsx,js,jsx}"));
184
- const sourceFiles = tsProject.getSourceFiles();
185
- return { content: [{ type: "text", text: `Directory contains ${sourceFiles.length} source files. Use a specific file path for deep dependency analysis.` }] };
186
- } else {
187
- const sourceFile = tsProject.addSourceFileAtPath(fullPath);
188
- const imports = sourceFile.getImportDeclarations();
189
- const importDetails = imports.map((imp) => {
190
- const moduleSpecifier = imp.getModuleSpecifierValue();
191
- const source = imp.getModuleSpecifierSourceFile();
192
- const resolvedPath = source ? path.relative(projectRoot, source.getFilePath()) : "unresolved/external";
193
- return `- ${moduleSpecifier} (${resolvedPath})`;
194
- });
195
- return { content: [{ type: "text", text: `Dependencies for ${targetPath}:\n${importDetails.length > 0 ? importDetails.join("\n") : "No imports found."}` }] };
196
- }
197
- } catch (error) {
198
- return { content: [{ type: "text", text: "Analysis failed: " + (error instanceof Error ? error.message : String(error)) }] };
199
- }
200
- },
201
- analyze_codebase_intelligence: async (args: unknown, projectRoot: string) => {
202
- const parsed = ANALYZE_CODEBASE_INTELLIGENCE_ARGS_SCHEMA.safeParse(args);
203
- const targetPath = parsed.success ? parsed.data.path : ".";
204
- try {
205
- const safeTargetPath = resolveSafePath(projectRoot, targetPath);
206
- if (!fs.existsSync(safeTargetPath)) return { content: [{ type: "text", text: "Target path not found." }] };
207
- const tsProject = new Project({ compilerOptions: { allowJs: true } });
208
- tsProject.addSourceFilesAtPaths(path.join(safeTargetPath, "**/*.{ts,tsx}"));
209
- const complexitySpikes: string[] = [];
210
- const potentialDeadCode: string[] = [];
211
- for (const sourceFile of tsProject.getSourceFiles()) {
212
- const relativePath = path.relative(projectRoot, sourceFile.getFilePath());
213
- const lineCount = sourceFile.getEndLineNumber();
214
- if (lineCount > 300) complexitySpikes.push(`${relativePath} (${lineCount} lines)`);
215
- sourceFile.getExportedDeclarations().forEach((declarations, name) => {
216
- const isUsed = tsProject.getSourceFiles().some(sf => {
217
- if (sf === sourceFile) return false;
218
- return sf.getImportDeclarations().some(i => i.getModuleSpecifierValue().includes(sourceFile.getBaseNameWithoutExtension()) && i.getNamedImports().some(ni => ni.getName() === name));
219
- });
220
- if (!isUsed && !sourceFile.getFilePath().includes("index.ts") && !sourceFile.getFilePath().includes("shared-types")) {
221
- potentialDeadCode.push(`${relativePath} -> ${name}`);
222
- }
223
- });
224
- }
225
- return { content: [{ type: "text", text: `### CODEBASE INTELLIGENCE REPORT\n\n**Complexity Spikes (>300 lines):**\n${complexitySpikes.length > 0 ? complexitySpikes.map(s => `- ${s}`).join("\n") : "- None detected"}\n\n**Potential Dead Code (Unused Exports):**\n${potentialDeadCode.length > 0 ? potentialDeadCode.map(d => `- ${d}`).join("\n") : "- None detected (within scanned scope)"}` }] };
226
- } catch {
227
- return { content: [{ type: "text", text: "Intelligence scan failed." }] };
228
- }
229
- },
230
- analyze_procedural_continuity: async (args: unknown, projectRoot: string) => {
231
- const parsed = ANALYZE_PROCEDURAL_CONTINUITY_ARGS_SCHEMA.safeParse(args);
232
- if (!parsed.success) return { content: [{ type: "text", text: "Invalid targetPath or referencePath." }] };
233
- try {
234
- const targetFull = resolveSafePath(projectRoot, parsed.data.targetPath);
235
- const referenceFull = resolveSafePath(projectRoot, parsed.data.referencePath);
236
- if (!fs.existsSync(targetFull) || !fs.existsSync(referenceFull)) return { content: [{ type: "text", text: "One or both files not found." }] };
237
- const tsProject = new Project({ compilerOptions: { allowJs: true } });
238
- const targetFile = tsProject.addSourceFileAtPath(targetFull);
239
- const referenceFile = tsProject.addSourceFileAtPath(referenceFull);
240
- const deviations: string[] = [];
241
- const targetImports = targetFile.getImportDeclarations().map(i => i.getModuleSpecifierValue());
242
- const referenceImports = referenceFile.getImportDeclarations().map(i => i.getModuleSpecifierValue());
243
- const missingImports = referenceImports.filter(i => !targetImports.includes(i) && !i.startsWith("."));
244
- if (missingImports.length > 0) deviations.push(`Missing standard imports found in reference: ${missingImports.join(", ")}`);
245
- if (referenceFile.getClasses().length > 0 && targetFile.getClasses().length === 0) deviations.push("Reference uses classes, but target does not.");
246
- const targetFunctions = targetFile.getFunctions().map(f => f.getName());
247
- const referenceFunctions = referenceFile.getFunctions().map(f => f.getName());
248
- if (referenceFunctions.some(f => f?.startsWith("get")) && !targetFunctions.some(f => f?.startsWith("get"))) deviations.push("Reference uses 'get' prefix for functions, target does not.");
249
- return { content: [{ type: "text", text: deviations.length > 0 ? `### PROCEDURAL CONTINUITY DEVIATIONS\n\n${deviations.map(d => `- ${d}`).join("\n")}` : "No major structural or stylistic deviations detected based on reference." }] };
250
- } catch {
251
- return { content: [{ type: "text", text: "Dependency analysis failed." }] };
252
- }
253
- },
254
- generate_dependency_graph: async (args: unknown, projectRoot: string) => {
255
- const parsed = GENERATE_DEPENDENCY_GRAPH_ARGS_SCHEMA.safeParse(args);
256
- const targetPath = parsed.success ? parsed.data.path : "src";
257
- const format = parsed.success ? parsed.data.format : "mermaid";
258
- try {
259
- const safeTargetPath = resolveSafePath(projectRoot, targetPath);
260
- if (!fs.existsSync(safeTargetPath)) return { content: [{ type: "text", text: "Target path not found." }] };
261
- const tsProject = new Project({ compilerOptions: { allowJs: true } });
262
- tsProject.addSourceFilesAtPaths(path.join(safeTargetPath, "**/*.{ts,tsx}"));
263
- const edges: { from: string; to: string }[] = [];
264
- for (const sourceFile of tsProject.getSourceFiles()) {
265
- const from = sourceFile.getBaseNameWithoutExtension();
266
- sourceFile.getImportDeclarations().forEach(imp => {
267
- const moduleSpecifier = imp.getModuleSpecifierValue();
268
- if (moduleSpecifier.startsWith(".")) {
269
- const to = path.basename(moduleSpecifier, path.extname(moduleSpecifier));
270
- edges.push({ from, to });
271
- }
272
- });
273
- }
274
- if (format === "json") return { content: [{ type: "text", text: JSON.stringify(edges, null, 2) }] };
275
- let mermaid = "graph TD\n";
276
- const uniqueEdges = Array.from(new Set(edges.map(e => `${e.from} --> ${e.to}`)));
277
- if (uniqueEdges.length === 0) return { content: [{ type: "text", text: "No internal dependencies found in the scanned path." }] };
278
- mermaid += uniqueEdges.join("\n");
279
- return { content: [{ type: "text", text: `### VISUAL ARCHITECTURE MAP: ${targetPath}\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`\n\n**Total Nodes:** ${new Set(edges.flatMap(e => [e.from, e.to])).size} | **Total Edges:** ${uniqueEdges.length}` }] };
280
- } catch {
281
- return { content: [{ type: "text", text: "Dependency graph generation failed." }] };
282
- }
283
- },
284
- };
@@ -1,221 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import crypto from "crypto";
4
- import { Project } from "ts-morph";
5
- import {
6
- getFrameworkDir,
7
- collectFilesRecursively
8
- } from "../utils.js";
9
- import { VERIFY_CONTRACT_INTEGRITY_ARGS_SCHEMA } from "../schemas.js";
10
-
11
- function calculateContractHash(sharedTypesDir: string, projectRoot: string): string {
12
- const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"])).sort();
13
- const hash = crypto.createHash("sha256");
14
- files.forEach((filePath) => {
15
- hash.update(path.relative(projectRoot, filePath));
16
- hash.update("\0");
17
- hash.update(fs.readFileSync(filePath));
18
- hash.update("\0");
19
- });
20
- return hash.digest("hex");
21
- }
22
-
23
- export const contractTools = [
24
- {
25
- name: "verify_api_contract",
26
- description: "Verify if the shared-types match the stored contract hash in contract.version.json.",
27
- inputSchema: { type: "object", properties: {} },
28
- },
29
- {
30
- name: "update_contract_hash",
31
- description: "Generate a new hash for shared-types and update contract.version.json.",
32
- inputSchema: { type: "object", properties: {} },
33
- },
34
- {
35
- name: "verify_contract_integrity",
36
- description: "Deeply analyzes API documentation and shared types to ensure they are perfectly synchronized.",
37
- inputSchema: {
38
- type: "object",
39
- properties: {
40
- domain: {
41
- type: "string",
42
- description: "The API domain to verify (e.g. 'auth', 'user')",
43
- },
44
- },
45
- required: ["domain"],
46
- },
47
- },
48
- {
49
- name: "validate_frontend_api_contract",
50
- description: "Scans frontend hooks for API calls and compares them against backend API documentation and contracts. Returns mismatches (missing endpoints, type issues, etc.).",
51
- inputSchema: {
52
- type: "object",
53
- properties: {
54
- frontendHooksPath: {
55
- type: "string",
56
- description: "Relative path to frontend hooks directory (default: apps/web/src/pages)",
57
- },
58
- backendApiDocsPath: {
59
- type: "string",
60
- description: "Relative path to backend API documentation (default: docs/api)",
61
- },
62
- },
63
- },
64
- },
65
- ];
66
-
67
- export const contractHandlers = {
68
- verify_api_contract: async (args: unknown, projectRoot: string) => {
69
- try {
70
- const sharedTypesDir = path.join(projectRoot, "apps/backend/src/types");
71
- const contractJsonPath = path.join(projectRoot, "apps/backend/contract.version.json");
72
- if (!fs.existsSync(sharedTypesDir) || !fs.existsSync(contractJsonPath)) return { content: [{ type: "text", text: "Missing shared-types directory or contract.version.json" }] };
73
- const currentHash = calculateContractHash(sharedTypesDir, projectRoot);
74
- const storedHash = JSON.parse(fs.readFileSync(contractJsonPath, "utf-8")).contract_hash;
75
- return { content: [{ type: "text", text: currentHash === storedHash ? "✅ MATCH: Contract is valid and synchronized." : `❌ MISMATCH: Current hash (${currentHash.slice(0, 8)}...) does not match stored hash (${storedHash.slice(0, 8)}...).` }] };
76
- } catch {
77
- return { content: [{ type: "text", text: "Contract integrity check failed." }] };
78
- }
79
- },
80
- update_contract_hash: async (args: unknown, projectRoot: string) => {
81
- try {
82
- const sharedTypesDir = path.join(projectRoot, "apps/backend/src/types");
83
- const contractJsonPath = path.join(projectRoot, "apps/backend/contract.version.json");
84
- if (!fs.existsSync(sharedTypesDir)) return { content: [{ type: "text", text: "Missing shared-types directory" }] };
85
- const files = collectFilesRecursively(sharedTypesDir, new Set(["ts"])).sort();
86
- if (files.length === 0) return { content: [{ type: "text", text: "⚠️ WARNING: No TypeScript files found in shared-types/src. Hash not updated." }] };
87
- const currentHash = calculateContractHash(sharedTypesDir, projectRoot);
88
- const contractJson = fs.existsSync(contractJsonPath) ? JSON.parse(fs.readFileSync(contractJsonPath, "utf-8")) : {};
89
- contractJson.contract_hash = currentHash;
90
- contractJson.last_updated = new Date().toISOString();
91
- fs.writeFileSync(contractJsonPath, JSON.stringify(contractJson, null, 2));
92
- return { content: [{ type: "text", text: `SUCCESS: Contract hash updated to ${currentHash}` }] };
93
- } catch {
94
- return { content: [{ type: "text", text: "Failed to update contract hash." }] };
95
- }
96
- },
97
- verify_contract_integrity: async (args: unknown, projectRoot: string) => {
98
- const parsed = VERIFY_CONTRACT_INTEGRITY_ARGS_SCHEMA.safeParse(args ?? {});
99
- if (!parsed.success) return { content: [{ type: "text", text: "Invalid domain argument." }] };
100
- try {
101
- const frameworkDir = getFrameworkDir(projectRoot);
102
- const apiDocPath = path.join(projectRoot, frameworkDir, "docs/api", `${parsed.data.domain}.md`);
103
- const sharedTypesDir = path.join(projectRoot, "apps/backend/src/types");
104
-
105
- if (!fs.existsSync(apiDocPath)) return { content: [{ type: "text", text: `API documentation not found for domain: ${parsed.data.domain}` }] };
106
- if (!fs.existsSync(sharedTypesDir)) return { content: [{ type: "text", text: "Shared types directory not found." }] };
107
-
108
- const project = new Project();
109
- project.addSourceFilesAtPaths(path.join(sharedTypesDir, "**/*.ts"));
110
-
111
- const mentionedTypes = Array.from(fs.readFileSync(apiDocPath, "utf-8").matchAll(/`([^`]+)`/g))
112
- .map(m => m[1])
113
- .filter(t => /^[A-Z][a-zA-Z0-9]+(DTO|Response|Request|Status|Type)?$/.test(t));
114
-
115
- const uniqueTypes = Array.from(new Set(mentionedTypes));
116
- const missingTypes = uniqueTypes.filter(t => {
117
- const found = project.getSourceFiles().some(sf =>
118
- sf.getInterface(t) || sf.getTypeAlias(t) || sf.getEnum(t) || sf.getClass(t)
119
- );
120
- return !found;
121
- });
122
-
123
- return { content: [{ type: "text", text: `### CONTRACT INTEGRITY SHIELD: ${parsed.data.domain.toUpperCase()}\n\n` + `- **Missing/Undefined Types:** ${missingTypes.length > 0 ? `⚠️ ${missingTypes.join(", ")}` : "✅ All types synchronized"}\n\n` + `**Result:** ${missingTypes.length === 0 ? "PASSED" : "FAILED"}` }] };
124
- } catch {
125
- return { content: [{ type: "text", text: "Contract verification failed." }] };
126
- }
127
- },
128
-
129
- validate_frontend_api_contract: async (args: unknown, projectRoot: string) => {
130
- const a = args as Record<string, string> | undefined;
131
- const frontendPath = a?.frontendHooksPath || "apps/web/src/pages";
132
- const backendDocsPath = a?.backendApiDocsPath || "docs/api";
133
-
134
- try {
135
- const fullFrontendPath = path.join(projectRoot, frontendPath);
136
- const fullBackendDocsPath = path.join(projectRoot, backendDocsPath);
137
-
138
- if (!fs.existsSync(fullFrontendPath)) {
139
- return { content: [{ type: "text", text: `Frontend hooks path not found: ${frontendPath}` }] };
140
- }
141
-
142
- const hookFiles = collectFilesRecursively(fullFrontendPath, new Set(["ts", "tsx"]));
143
- const apiCalls: { endpoint: string; responseType?: string }[] = [];
144
-
145
- // Hook dosyalarındaki fetch çağrılarını ve response tiplerini çıkar
146
- for (const file of hookFiles) {
147
- const content = fs.readFileSync(file, "utf-8");
148
-
149
- // Basit regex ile endpoint + response tipi yakala
150
- const matches = content.matchAll(
151
- /(?:const\s+\w+\s*[:=]\s*await\s+)?fetch\(['"`](\/api\/[^'"`]+)['"`][^)]*\)\s*(?:\.then[^;]+)?(?:\s*as\s+([A-Z][a-zA-Z0-9]+)|:\s*([A-Z][a-zA-Z0-9]+))?/g
152
- );
153
-
154
- for (const match of matches) {
155
- const endpoint = match[1];
156
- const responseType = match[2] || match[3];
157
- apiCalls.push({ endpoint, responseType });
158
- }
159
- }
160
-
161
- const uniqueEndpoints = Array.from(new Set(apiCalls.map(c => c.endpoint)));
162
-
163
- if (!fs.existsSync(fullBackendDocsPath)) {
164
- return { content: [{ type: "text", text: `Backend API docs path not found: ${backendDocsPath}` }] };
165
- }
166
-
167
- const backendDocFiles = collectFilesRecursively(fullBackendDocsPath, new Set(["md"]));
168
- const documentedEndpoints: string[] = [];
169
-
170
- for (const file of backendDocFiles) {
171
- const content = fs.readFileSync(file, "utf-8");
172
- const matches = content.matchAll(/`?(\/api\/[a-zA-Z0-9/_-]+)`?/g);
173
- for (const match of matches) {
174
- documentedEndpoints.push(match[1]);
175
- }
176
- }
177
-
178
- const missingInDocs = uniqueEndpoints.filter(ep => !documentedEndpoints.some(d => ep.startsWith(d)));
179
- const missingInHooks = documentedEndpoints.filter(d => !uniqueEndpoints.some(e => e.startsWith(d)));
180
-
181
- let report = `### FRONTEND ↔ BACKEND CONTRACT VALIDATION REPORT\n\n`;
182
- report += `**Scanned Frontend Hooks:** ${uniqueEndpoints.length} unique endpoints found\n`;
183
- report += `**Scanned Backend API Docs:** ${documentedEndpoints.length} endpoints documented\n\n`;
184
-
185
- if (missingInDocs.length > 0) {
186
- report += `⚠️ **Endpoints called from frontend but NOT documented in backend:**\n${missingInDocs.map(e => `- ${e}`).join("\n")}\n\n`;
187
- } else {
188
- report += `✅ All frontend-called endpoints are documented in backend.\n\n`;
189
- }
190
-
191
- if (missingInHooks.length > 0) {
192
- report += `ℹ️ **Endpoints documented in backend but NOT used in scanned frontend hooks:**\n${missingInHooks.map(e => `- ${e}`).join("\n")}\n\n`;
193
- }
194
-
195
- // Basit tip karşılaştırması (eğer responseType varsa)
196
- const typeMismatches: string[] = [];
197
- for (const call of apiCalls) {
198
- if (call.responseType) {
199
- const docContent = backendDocFiles
200
- .map(f => fs.readFileSync(f, "utf-8"))
201
- .join("\n");
202
-
203
- if (!docContent.includes(call.responseType)) {
204
- typeMismatches.push(`${call.endpoint} → expected type "${call.responseType}" not found in docs`);
205
- }
206
- }
207
- }
208
-
209
- if (typeMismatches.length > 0) {
210
- report += `⚠️ **Potential Type Mismatches:**\n${typeMismatches.map(m => `- ${m}`).join("\n")}\n\n`;
211
- }
212
-
213
- report += `**Result:** ${missingInDocs.length === 0 && typeMismatches.length === 0 ? "PASSED" : "FAILED (see mismatches above)"}`;
214
-
215
- return { content: [{ type: "text", text: report }] };
216
- } catch (err: unknown) {
217
- const message = err instanceof Error ? err.message : String(err);
218
- return { content: [{ type: "text", text: `Validation failed: ${message}` }] };
219
- }
220
- },
221
- };