@mandujs/mcp 0.37.2 → 0.37.4

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.
@@ -1,10 +1,13 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { type ManduError } from "@mandujs/core/error";
3
- import {
4
- loadManifest,
5
- runGuardCheck,
6
- runAutoCorrect,
7
- type GeneratedMap,
3
+ import {
4
+ checkDirectory,
5
+ getDefaultFsRoutesGuardPolicy,
6
+ validateAndReport,
7
+ loadManifest,
8
+ runGuardCheck,
9
+ runAutoCorrect,
10
+ type GeneratedMap,
8
11
  // Self-Healing Guard imports
9
12
  checkWithHealing,
10
13
  healAll,
@@ -12,16 +15,19 @@ import {
12
15
  // Follow-up E — type-aware lint bridge
13
16
  runTsgolint,
14
17
  type GuardConfig,
15
- type ViolationType,
16
- type GuardPreset,
17
- } from "@mandujs/core";
18
- import { getProjectPaths, readJsonFile, readConfig } from "../utils/project.js";
18
+ type ViolationType,
19
+ type GuardPreset,
20
+ type Violation,
21
+ } from "@mandujs/core";
22
+ import { getProjectPaths, readJsonFile, readConfig } from "../utils/project.js";
23
+ import fs from "fs/promises";
24
+ import path from "path";
19
25
 
20
26
  export const guardToolDefinitions: Tool[] = [
21
27
  {
22
- name: "mandu.guard.check",
23
- description:
24
- "Run guard checks to validate spec integrity, generated files, and slot files. Set typeAware=true to additionally run `oxlint --type-aware` (tsgolint) and merge its results.",
28
+ name: "mandu.guard.check",
29
+ description:
30
+ "Run the same architecture guard used by `mandu guard`, plus legacy spec/generated/slot checks. Set typeAware=true to additionally run `oxlint --type-aware` (tsgolint) and merge its results.",
25
31
  annotations: {
26
32
  readOnlyHint: true,
27
33
  },
@@ -120,8 +126,37 @@ export const guardToolDefinitions: Tool[] = [
120
126
  },
121
127
  ];
122
128
 
123
- export function guardTools(projectRoot: string) {
124
- const paths = getProjectPaths(projectRoot);
129
+ export function guardTools(projectRoot: string) {
130
+ const paths = getProjectPaths(projectRoot);
131
+
132
+ const pathExists = async (candidate: string): Promise<boolean> => {
133
+ try {
134
+ await fs.access(candidate);
135
+ return true;
136
+ } catch {
137
+ return false;
138
+ }
139
+ };
140
+
141
+ const summarizeArchitectureViolation = (violation: Violation) => ({
142
+ ruleId: violation.ruleName,
143
+ type: violation.type,
144
+ file: path.relative(projectRoot, violation.filePath).replace(/\\/g, "/") || violation.filePath,
145
+ line: violation.line,
146
+ column: violation.column,
147
+ message: violation.ruleDescription,
148
+ suggestion: violation.suggestions[0],
149
+ fromLayer: violation.fromLayer,
150
+ toLayer: violation.toLayer,
151
+ importStatement: violation.importStatement,
152
+ });
153
+
154
+ const summarizeLegacyViolation = (v: Awaited<ReturnType<typeof runGuardCheck>>["violations"][number]) => ({
155
+ ruleId: v.ruleId,
156
+ file: v.file,
157
+ message: v.message,
158
+ suggestion: v.suggestion,
159
+ });
125
160
 
126
161
  const handlers: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
127
162
  "mandu.guard.check": async (args: Record<string, unknown>) => {
@@ -137,25 +172,44 @@ export function guardTools(projectRoot: string) {
137
172
  error: "Failed to load manifest",
138
173
  details: manifestResult.errors,
139
174
  };
140
- }
141
-
142
- // Run guard check
143
- const checkResult = await runGuardCheck(manifestResult.data, projectRoot);
175
+ }
176
+
177
+ const projectConfig = await validateAndReport(projectRoot);
178
+ const guardConfigFromFile = (projectConfig?.guard ?? {}) as GuardConfig;
179
+ const preset = guardConfigFromFile.preset ?? "mandu";
180
+ const enableFsRoutes = await pathExists(paths.appDir);
181
+
182
+ const architectureReport = await checkDirectory(
183
+ {
184
+ preset,
185
+ srcDir: guardConfigFromFile.srcDir ?? "src",
186
+ exclude: guardConfigFromFile.exclude,
187
+ fsRoutes: getDefaultFsRoutesGuardPolicy(enableFsRoutes),
188
+ },
189
+ projectRoot
190
+ );
191
+ const architecturePassed = architectureReport.bySeverity.error === 0;
192
+ const architectureViolations = architectureReport.violations.map(
193
+ summarizeArchitectureViolation
194
+ );
195
+
196
+ // Run guard check
197
+ const checkResult = await runGuardCheck(manifestResult.data, projectRoot);
144
198
 
145
199
  // Follow-up E — resolve type-aware defaulting from config, then
146
200
  // run the bridge when enabled. Result envelope is always included
147
201
  // in the tool response so MCP clients have a single stable shape.
148
- let projectConfig: Awaited<ReturnType<typeof readConfig>> | undefined;
149
- try {
150
- projectConfig = await readConfig(projectRoot);
151
- } catch {
152
- projectConfig = undefined;
153
- }
154
- const typeAwareCfg = (
155
- projectConfig?.guard as
156
- | { typeAware?: Record<string, unknown> }
157
- | undefined
158
- )?.typeAware;
202
+ let rawProjectConfig: Awaited<ReturnType<typeof readConfig>> | undefined;
203
+ try {
204
+ rawProjectConfig = projectConfig ?? await readConfig(projectRoot);
205
+ } catch {
206
+ rawProjectConfig = projectConfig ?? undefined;
207
+ }
208
+ const typeAwareCfg = (
209
+ rawProjectConfig?.guard as
210
+ | { typeAware?: Record<string, unknown> }
211
+ | undefined
212
+ )?.typeAware;
159
213
  const typeAwareEnabled =
160
214
  typeAwareArg !== undefined ? typeAwareArg : typeAwareCfg !== undefined;
161
215
 
@@ -175,21 +229,40 @@ export function guardTools(projectRoot: string) {
175
229
  skipped: bridge.skipped,
176
230
  summary: bridge.summary,
177
231
  violations: bridge.violations,
178
- };
179
- }
180
-
181
- if (checkResult.passed) {
182
- return {
183
- passed: typeAwareResponse
184
- ? (typeAwareResponse.violations as Array<{ severity: string }>).filter(
185
- (v) => v.severity === "error",
186
- ).length === 0
187
- : true,
188
- violations: [],
189
- message: "All guard checks passed",
190
- relatedSkills: ["mandu-guard-guide", "mandu-debug"],
191
- ...(typeAwareResponse ? { typeAware: typeAwareResponse } : {}),
192
- };
232
+ };
233
+ }
234
+
235
+ const typeAwareViolations = typeAwareResponse
236
+ ? typeAwareResponse.violations as Array<{ severity: string }>
237
+ : [];
238
+ const typeAwareErrorCount = typeAwareViolations.filter(
239
+ (v) => v.severity === "error",
240
+ ).length;
241
+ const legacyViolations = checkResult.violations.map(summarizeLegacyViolation);
242
+ const combinedViolations = [
243
+ ...architectureViolations,
244
+ ...legacyViolations,
245
+ ];
246
+ const allPassed = checkResult.passed && architecturePassed && typeAwareErrorCount === 0;
247
+ const blockingViolationCount = combinedViolations.length + typeAwareErrorCount;
248
+
249
+ if (allPassed) {
250
+ return {
251
+ passed: true,
252
+ violations: [],
253
+ message: "All guard checks passed",
254
+ architecture: {
255
+ passed: true,
256
+ totalViolations: architectureReport.totalViolations,
257
+ bySeverity: architectureReport.bySeverity,
258
+ },
259
+ legacy: {
260
+ passed: true,
261
+ violations: 0,
262
+ },
263
+ relatedSkills: ["mandu-guard-guide", "mandu-debug"],
264
+ ...(typeAwareResponse ? { typeAware: typeAwareResponse } : {}),
265
+ };
193
266
  }
194
267
 
195
268
  // If auto-correct requested and there are violations
@@ -200,33 +273,54 @@ export function guardTools(projectRoot: string) {
200
273
  projectRoot
201
274
  );
202
275
 
203
- return {
204
- passed: autoCorrectResult.fixed,
205
- violations: autoCorrectResult.remainingViolations,
206
- autoCorrect: {
207
- attempted: true,
208
- fixed: autoCorrectResult.fixed,
276
+ return {
277
+ passed:
278
+ autoCorrectResult.fixed &&
279
+ architecturePassed &&
280
+ typeAwareErrorCount === 0,
281
+ violations: [
282
+ ...architectureViolations,
283
+ ...autoCorrectResult.remainingViolations.map(summarizeLegacyViolation),
284
+ ],
285
+ autoCorrect: {
286
+ attempted: true,
287
+ fixed: autoCorrectResult.fixed,
209
288
  steps: autoCorrectResult.steps,
210
289
  retriedCount: autoCorrectResult.retriedCount,
211
290
  rolledBack: autoCorrectResult.rolledBack,
212
- changeId: autoCorrectResult.changeId,
213
- },
214
- ...(typeAwareResponse ? { typeAware: typeAwareResponse } : {}),
215
- };
216
- }
217
-
218
- return {
219
- passed: false,
220
- violations: checkResult.violations.map((v) => ({
221
- ruleId: v.ruleId,
222
- file: v.file,
223
- message: v.message,
224
- suggestion: v.suggestion,
225
- })),
226
- message: `Found ${checkResult.violations.length} violation(s)`,
227
- tip: "Use autoCorrect: true to attempt automatic fixes",
228
- relatedSkills: ["mandu-guard-guide", "mandu-debug"],
229
- ...(typeAwareResponse ? { typeAware: typeAwareResponse } : {}),
291
+ changeId: autoCorrectResult.changeId,
292
+ },
293
+ architecture: {
294
+ passed: architecturePassed,
295
+ totalViolations: architectureReport.totalViolations,
296
+ bySeverity: architectureReport.bySeverity,
297
+ violations: architectureViolations,
298
+ },
299
+ legacy: {
300
+ passed: autoCorrectResult.fixed,
301
+ violations: autoCorrectResult.remainingViolations.length,
302
+ },
303
+ ...(typeAwareResponse ? { typeAware: typeAwareResponse } : {}),
304
+ };
305
+ }
306
+
307
+ return {
308
+ passed: false,
309
+ violations: combinedViolations,
310
+ message: `Found ${blockingViolationCount} violation(s)`,
311
+ architecture: {
312
+ passed: architecturePassed,
313
+ totalViolations: architectureReport.totalViolations,
314
+ bySeverity: architectureReport.bySeverity,
315
+ violations: architectureViolations,
316
+ },
317
+ legacy: {
318
+ passed: checkResult.passed,
319
+ violations: legacyViolations.length,
320
+ },
321
+ tip: "Use autoCorrect: true to attempt automatic fixes",
322
+ relatedSkills: ["mandu-guard-guide", "mandu-debug"],
323
+ ...(typeAwareResponse ? { typeAware: typeAwareResponse } : {}),
230
324
  };
231
325
  },
232
326
 
@@ -11,8 +11,9 @@ import { mcpToolRegistry } from "../registry/mcp-tool-registry.js";
11
11
  import { moduleToPlugins } from "../adapters/tool-adapter.js";
12
12
  import { type McpProfile, getProfileCategories } from "../profiles.js";
13
13
 
14
- // 도구 모듈 export
15
- export { specTools, specToolDefinitions } from "./spec.js";
14
+ // 도구 모듈 export
15
+ export { agentTools, agentToolDefinitions } from "./agent.js";
16
+ export { specTools, specToolDefinitions } from "./spec.js";
16
17
  export { generateTools, generateToolDefinitions } from "./generate.js";
17
18
  export { transactionTools, transactionToolDefinitions } from "./transaction.js";
18
19
  export { historyTools, historyToolDefinitions } from "./history.js";
@@ -85,8 +86,9 @@ export {
85
86
  extractContractToolDefinitions,
86
87
  } from "./extract-contract.js";
87
88
 
88
- // 도구 모듈 import (등록용)
89
- import { specTools, specToolDefinitions } from "./spec.js";
89
+ // 도구 모듈 import (등록용)
90
+ import { agentTools, agentToolDefinitions } from "./agent.js";
91
+ import { specTools, specToolDefinitions } from "./spec.js";
90
92
  import { generateTools, generateToolDefinitions } from "./generate.js";
91
93
  import { transactionTools, transactionToolDefinitions } from "./transaction.js";
92
94
  import { historyTools, historyToolDefinitions } from "./history.js";
@@ -189,7 +191,8 @@ export interface ToolModule {
189
191
  * 빌트인 도구 모듈 목록
190
192
  */
191
193
  export const TOOL_MODULES: ToolModule[] = [
192
- { category: "spec", definitions: specToolDefinitions, handlers: specTools },
194
+ { category: "agent", definitions: agentToolDefinitions, handlers: agentTools },
195
+ { category: "spec", definitions: specToolDefinitions, handlers: specTools },
193
196
  { category: "generate", definitions: generateToolDefinitions, handlers: generateTools },
194
197
  { category: "transaction", definitions: transactionToolDefinitions, handlers: transactionTools },
195
198
  { category: "history", definitions: historyToolDefinitions, handlers: historyTools },