@nxuss/lemma 1.0.2 → 1.0.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.
@@ -136,6 +136,18 @@ const toolDefinitions = [
136
136
  required: ["filePath", "content"],
137
137
  },
138
138
  },
139
+ {
140
+ name: "create_workspace_file",
141
+ description: "Create a NEW file in the workspace. Fails immediately if the file already exists — use write_workspace_file or apply_workspace_patch to modify existing files. Creates parent directories automatically. Returns a minimal token-free ACK: no file content is echoed back, saving provider output tokens.",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ filePath: { type: "string", description: "Path relative to the project root for the new file" },
146
+ content: { type: "string", description: "The complete initial content for the new file" },
147
+ },
148
+ required: ["filePath", "content"],
149
+ },
150
+ },
139
151
  {
140
152
  name: "apply_workspace_patch",
141
153
  description: "Apply a smart search-and-replace patch to an existing file. Safe against duplicate matches.",
@@ -596,6 +608,172 @@ const toolDefinitions = [
596
608
  required: ["operation", "from", "to"],
597
609
  },
598
610
  },
611
+ {
612
+ name: "smart_file_slice",
613
+ description: "Read only the lines semánticamente relevantes around a search query in a file, avoiding sending the entire file context. Returns lines around the matches.",
614
+ inputSchema: {
615
+ type: "object",
616
+ properties: {
617
+ filePath: { type: "string", description: "Path relative to the project root" },
618
+ query: { type: "string", description: "The term or pattern to look for" },
619
+ windowLines: { type: "number", description: "Number of lines of context around each match (default: 10)", default: 10 }
620
+ },
621
+ required: ["filePath", "query"]
622
+ }
623
+ },
624
+ {
625
+ name: "test_oracle",
626
+ description: "Execute unit tests locally and output ONLY the failing tests and compressed stack traces, keeping context clean.",
627
+ inputSchema: {
628
+ type: "object",
629
+ properties: {
630
+ command: { type: "string", description: "Test execution command (default: 'npm test')", default: "npm test" }
631
+ }
632
+ }
633
+ },
634
+ {
635
+ name: "schema_extract",
636
+ description: "Extract high-level schemas, Zod types, interfaces, or database models from a file using the TypeScript AST, removing all implementation code.",
637
+ inputSchema: {
638
+ type: "object",
639
+ properties: {
640
+ filePath: { type: "string", description: "Path relative to the project root" }
641
+ },
642
+ required: ["filePath"]
643
+ }
644
+ },
645
+ {
646
+ name: "changelog_auto",
647
+ description: "Generate a clean, token-efficient changelog summary from git logs based on Conventional Commits.",
648
+ inputSchema: {
649
+ type: "object",
650
+ properties: {
651
+ limit: { type: "number", description: "Number of git commits to analyze (default: 30)", default: 30 }
652
+ }
653
+ }
654
+ },
655
+ {
656
+ name: "spec_to_stub",
657
+ description: "Parse a TypeScript interface/type definition from a file and output a template mock/stub object configuration using pure AST analysis.",
658
+ inputSchema: {
659
+ type: "object",
660
+ properties: {
661
+ filePath: { type: "string", description: "Path relative to the project root" },
662
+ symbolName: { type: "string", description: "Name of the interface or type alias to stub" }
663
+ },
664
+ required: ["filePath", "symbolName"]
665
+ }
666
+ },
667
+ {
668
+ name: "env_snapshot",
669
+ description: "Capture environmental state like Node version, dependencies in package.json, and variables declared in .env files (hiding actual values/credentials).",
670
+ inputSchema: {
671
+ type: "object",
672
+ properties: {}
673
+ }
674
+ },
675
+ {
676
+ name: "migration_tracer",
677
+ description: "Inspect changes in database schemas (tables, columns) across local migrations (e.g. Prisma migration directories).",
678
+ inputSchema: {
679
+ type: "object",
680
+ properties: {
681
+ migrationsPath: { type: "string", description: "Path to migrations folder relative to workspace root (default: 'prisma/migrations')", default: "prisma/migrations" }
682
+ }
683
+ }
684
+ },
685
+ {
686
+ name: "multi_file_patch",
687
+ description: "Apply search-and-replace patches to multiple workspace files in a single turn. Decreases round-trips for multi-file refactoring.",
688
+ inputSchema: {
689
+ type: "object",
690
+ properties: {
691
+ patches: {
692
+ type: "array",
693
+ items: {
694
+ type: "object",
695
+ properties: {
696
+ filePath: { type: "string", description: "Path relative to the project root" },
697
+ searchContent: { type: "string", description: "The exact block of code to search for" },
698
+ replaceContent: { type: "string", description: "The block of code to replace it with" }
699
+ },
700
+ required: ["filePath", "searchContent", "replaceContent"]
701
+ },
702
+ description: "List of file patches to apply"
703
+ },
704
+ dryRun: { type: "boolean", description: "If true, returns preview diffs without modifying files", default: false }
705
+ },
706
+ required: ["patches"]
707
+ }
708
+ },
709
+ {
710
+ name: "file_intent_index",
711
+ description: "Map and index files inside the workspace by domain intent (e.g., auth, billing, routes, config) to speed up navigation.",
712
+ inputSchema: {
713
+ type: "object",
714
+ properties: {
715
+ dirPath: { type: "string", description: "Target directory relative to project root (default: '')", default: "" }
716
+ }
717
+ }
718
+ },
719
+ {
720
+ name: "cognitive_map",
721
+ description: "Access and interact with the Auto-Cognitive Mind Map of the workspace (persisted globally). Holds structured high-level system domains, patterns, decisions, and bugs.",
722
+ inputSchema: {
723
+ type: "object",
724
+ properties: {
725
+ action: { type: "string", enum: ["build", "get", "query", "update", "file_context"], description: "Action to perform", default: "get" },
726
+ domain: { type: "string", description: "Target domain for 'query' or 'update'" },
727
+ filePath: { type: "string", description: "Target file path for 'file_context'" },
728
+ updates: {
729
+ type: "object",
730
+ properties: {
731
+ patterns: { type: "array", items: { type: "string" } },
732
+ knownBugs: { type: "array", items: { type: "string" } },
733
+ decisions: { type: "array", items: { type: "string" } }
734
+ },
735
+ description: "Metadata to append/merge into the specified domain"
736
+ }
737
+ },
738
+ required: ["action"]
739
+ }
740
+ },
741
+ {
742
+ name: "semantic_grep",
743
+ description: "Search the codebase for conceptual terms using natural language and BM25 token relevance instead of exact substring matching.",
744
+ inputSchema: {
745
+ type: "object",
746
+ properties: {
747
+ query: { type: "string", description: "Natural language concept to search (e.g., 'jwt timeout expiration')" },
748
+ dirPath: { type: "string", description: "Limit search to folder path (default: '')", default: "" },
749
+ limit: { type: "number", description: "Maximum file matches (default: 5)", default: 5 }
750
+ },
751
+ required: ["query"]
752
+ }
753
+ },
754
+ {
755
+ name: "imports_skeleton_resolver",
756
+ description: "Resolve all imported local files inside a file and print only their signatures (classes, functions, interfaces) in a consolidated view.",
757
+ inputSchema: {
758
+ type: "object",
759
+ properties: {
760
+ filePath: { type: "string", description: "Path to the target file relative to project root" }
761
+ },
762
+ required: ["filePath"]
763
+ }
764
+ },
765
+ {
766
+ name: "ast_flow_visualizer",
767
+ description: "Analyze a function's control flow statements (ifs, loops, try/catch) inside a file using AST compilation and output a visual flowchart in Mermaid syntax.",
768
+ inputSchema: {
769
+ type: "object",
770
+ properties: {
771
+ filePath: { type: "string", description: "Path to the target file relative to project root" },
772
+ functionName: { type: "string", description: "Name of the function or method to visualize" }
773
+ },
774
+ required: ["filePath", "functionName"]
775
+ }
776
+ }
599
777
  ];
600
778
  const toolHandlers = {
601
779
  scrub_privacy: handleScrubPrivacy,
@@ -605,6 +783,7 @@ const toolHandlers = {
605
783
  auto_heal: handleAutoHeal,
606
784
  read_workspace_file: handleReadWorkspaceFile,
607
785
  write_workspace_file: handleWriteWorkspaceFile,
786
+ create_workspace_file: handleCreateWorkspaceFile,
608
787
  apply_workspace_patch: handleApplyWorkspacePatch,
609
788
  run_workspace_command: handleRunWorkspaceCommand,
610
789
  list_workspace_dir: handleListWorkspaceDir,
@@ -641,6 +820,19 @@ const toolHandlers = {
641
820
  generate_pr_workflow: handleGeneratePRWorkflow,
642
821
  depgraph: handleDepgraph,
643
822
  refactor: handleRefactor,
823
+ smart_file_slice: handleSmartFileSlice,
824
+ test_oracle: handleTestOracle,
825
+ schema_extract: handleSchemaExtract,
826
+ changelog_auto: handleChangelogAuto,
827
+ spec_to_stub: handleSpecToStub,
828
+ env_snapshot: handleEnvSnapshot,
829
+ migration_tracer: handleMigrationTracer,
830
+ multi_file_patch: handleMultiFilePatch,
831
+ file_intent_index: handleFileIntentIndex,
832
+ cognitive_map: handleCognitiveMap,
833
+ semantic_grep: handleSemanticGrep,
834
+ imports_skeleton_resolver: handleImportsSkeletonResolver,
835
+ ast_flow_visualizer: handleAstFlowVisualizer,
644
836
  };
645
837
  export function setupToolsHandlers(server, onToolCall) {
646
838
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -845,6 +1037,31 @@ async function handleWriteWorkspaceFile(args) {
845
1037
  return { content: [{ type: "text", text: `Error writing file: ${err.message}` }] };
846
1038
  }
847
1039
  }
1040
+ // ── Create Workspace File (zero-token ACK) ───────────────────────────
1041
+ async function handleCreateWorkspaceFile(args) {
1042
+ const filePath = args?.filePath;
1043
+ const content = args?.content;
1044
+ if (!filePath || content === undefined)
1045
+ throw new Error("filePath and content are required");
1046
+ const workspaceRoot = process.cwd();
1047
+ try {
1048
+ const { resolved } = safeResolvePath(workspaceRoot, filePath);
1049
+ if (fs.existsSync(resolved)) {
1050
+ return { content: [{ type: "text", text: `Error: File already exists at ${filePath}. Use write_workspace_file or apply_workspace_patch to modify it.` }] };
1051
+ }
1052
+ const dir = path.dirname(resolved);
1053
+ if (!fs.existsSync(dir)) {
1054
+ fs.mkdirSync(dir, { recursive: true });
1055
+ }
1056
+ fs.writeFileSync(resolved, content, "utf8");
1057
+ // Minimal ACK — no content echoed back to save provider output tokens
1058
+ return { content: [{ type: "text", text: `OK:created:${filePath}` }] };
1059
+ }
1060
+ catch (err) {
1061
+ logError("create_workspace_file", err);
1062
+ return { content: [{ type: "text", text: `Error creating file: ${err.message}` }] };
1063
+ }
1064
+ }
848
1065
  async function handleApplyWorkspacePatch(args) {
849
1066
  const filePath = args?.filePath;
850
1067
  const searchContent = args?.searchContent;
@@ -3466,4 +3683,762 @@ async function handleLocalSemanticAutofix(args) {
3466
3683
  };
3467
3684
  }
3468
3685
  }
3686
+ // ── Smart File Slice ────────────────────────────────────────────────
3687
+ async function handleSmartFileSlice(args) {
3688
+ const filePath = args.filePath;
3689
+ const query = args.query;
3690
+ const windowLines = typeof args.windowLines === "number" ? args.windowLines : 10;
3691
+ const workspaceRoot = process.cwd();
3692
+ try {
3693
+ const { resolved } = safeResolvePath(workspaceRoot, filePath);
3694
+ const content = fs.readFileSync(resolved, "utf8");
3695
+ const lines = content.split("\n");
3696
+ const matchedIndices = [];
3697
+ const lowerQuery = query.toLowerCase();
3698
+ lines.forEach((line, index) => {
3699
+ if (line.toLowerCase().includes(lowerQuery)) {
3700
+ matchedIndices.push(index);
3701
+ }
3702
+ });
3703
+ if (matchedIndices.length === 0) {
3704
+ return { content: [{ type: "text", text: `No matches found in ${filePath} for "${query}"` }] };
3705
+ }
3706
+ // Merge overlapping windows
3707
+ const windows = [];
3708
+ matchedIndices.forEach((idx) => {
3709
+ const start = Math.max(0, idx - windowLines);
3710
+ const end = Math.min(lines.length - 1, idx + windowLines);
3711
+ if (windows.length > 0 && windows[windows.length - 1].end >= start) {
3712
+ windows[windows.length - 1].end = Math.max(windows[windows.length - 1].end, end);
3713
+ }
3714
+ else {
3715
+ windows.push({ start, end });
3716
+ }
3717
+ });
3718
+ let resultText = `Slicing ${filePath} around query "${query}":\n\n`;
3719
+ windows.forEach((win, index) => {
3720
+ if (index > 0)
3721
+ resultText += "\n... [omitted] ...\n\n";
3722
+ for (let i = win.start; i <= win.end; i++) {
3723
+ resultText += `${i + 1}: ${lines[i]}\n`;
3724
+ }
3725
+ });
3726
+ return { content: [{ type: "text", text: resultText }] };
3727
+ }
3728
+ catch (e) {
3729
+ return { content: [{ type: "text", text: `Error slicing file: ${e.message}` }] };
3730
+ }
3731
+ }
3732
+ // ── Test Oracle ──────────────────────────────────────────────────────
3733
+ async function handleTestOracle(args) {
3734
+ const command = args.command || "npm test";
3735
+ const workspaceRoot = process.cwd();
3736
+ // Run tests, check errors
3737
+ try {
3738
+ const out = execSync(command, { cwd: workspaceRoot, encoding: "utf8", timeout: 30000 });
3739
+ return { content: [{ type: "text", text: `All tests passed!\n\nOutput:\n${out.substring(0, 1000)}` }] };
3740
+ }
3741
+ catch (e) {
3742
+ const stdout = e.stdout || "";
3743
+ const stderr = e.stderr || e.message || "";
3744
+ // Attempt to parse only failures/stack traces
3745
+ const failureLines = (stdout + "\n" + stderr)
3746
+ .split("\n")
3747
+ .filter((l) => l.includes("fail") || l.includes("Error") || l.includes("at ") || l.includes("✗") || l.includes("Stack"));
3748
+ const summary = failureLines.slice(0, 80).join("\n") || `Test execution failed:\n${stdout.substring(0, 800)}\n${stderr.substring(0, 800)}`;
3749
+ return { content: [{ type: "text", text: `Tests Failed. Compressed failures:\n\n${summary}` }] };
3750
+ }
3751
+ }
3752
+ // ── Schema Extract ───────────────────────────────────────────────────
3753
+ async function handleSchemaExtract(args) {
3754
+ const filePath = args.filePath;
3755
+ const workspaceRoot = process.cwd();
3756
+ try {
3757
+ const { resolved } = safeResolvePath(workspaceRoot, filePath);
3758
+ const content = fs.readFileSync(resolved, "utf8");
3759
+ const sourceFile = ts.createSourceFile(resolved, content, ts.ScriptTarget.Latest, true);
3760
+ const schemas = [];
3761
+ function visit(node) {
3762
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
3763
+ // Grab declarations without implementation details or method bodies
3764
+ const printer = ts.createPrinter({ removeComments: true });
3765
+ const printed = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
3766
+ schemas.push(printed);
3767
+ }
3768
+ else if (ts.isVariableStatement(node)) {
3769
+ // Look for schema variables like Zod schemas: const userSchema = z.object(...)
3770
+ for (const decl of node.declarationList.declarations) {
3771
+ if (decl.name && ts.isIdentifier(decl.name) && decl.name.text.toLowerCase().includes("schema")) {
3772
+ const printer = ts.createPrinter({ removeComments: true });
3773
+ const printed = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
3774
+ schemas.push(printed);
3775
+ }
3776
+ }
3777
+ }
3778
+ ts.forEachChild(node, visit);
3779
+ }
3780
+ visit(sourceFile);
3781
+ if (schemas.length === 0) {
3782
+ // Fallback: search lines for Zod schema or interfaces
3783
+ const lines = content.split("\n").filter(l => l.includes("interface ") || l.includes("type ") || l.includes("enum ") || l.includes("Schema ="));
3784
+ return { content: [{ type: "text", text: `No high-level AST schema declarations found. High level declarations:\n\n${lines.join("\n")}` }] };
3785
+ }
3786
+ return { content: [{ type: "text", text: schemas.join("\n\n") }] };
3787
+ }
3788
+ catch (e) {
3789
+ return { content: [{ type: "text", text: `Schema extraction failed: ${e.message}` }] };
3790
+ }
3791
+ }
3792
+ // ── Changelog Auto ───────────────────────────────────────────────────
3793
+ async function handleChangelogAuto(args) {
3794
+ const limit = typeof args.limit === "number" ? args.limit : 30;
3795
+ const workspaceRoot = process.cwd();
3796
+ try {
3797
+ const log = execSync(`git log -n ${limit} --oneline`, { cwd: workspaceRoot, encoding: "utf8" });
3798
+ const commits = log.split("\n").filter(Boolean);
3799
+ const feats = [];
3800
+ const fixes = [];
3801
+ const chore = [];
3802
+ const docs = [];
3803
+ const refactors = [];
3804
+ const others = [];
3805
+ commits.forEach((c) => {
3806
+ const message = c.substring(8); // Strip hash
3807
+ if (message.startsWith("feat"))
3808
+ feats.push(c);
3809
+ else if (message.startsWith("fix"))
3810
+ fixes.push(c);
3811
+ else if (message.startsWith("chore"))
3812
+ chore.push(c);
3813
+ else if (message.startsWith("docs"))
3814
+ docs.push(c);
3815
+ else if (message.startsWith("refactor"))
3816
+ refactors.push(c);
3817
+ else
3818
+ others.push(c);
3819
+ });
3820
+ let output = `## Git Changelog (Last ${limit} commits)\n\n`;
3821
+ if (feats.length)
3822
+ output += `### 🚀 Features\n${feats.map(x => `- ${x}`).join("\n")}\n\n`;
3823
+ if (fixes.length)
3824
+ output += `### 🐛 Bug Fixes\n${fixes.map(x => `- ${x}`).join("\n")}\n\n`;
3825
+ if (refactors.length)
3826
+ output += `### 🛠️ Refactoring\n${refactors.map(x => `- ${x}`).join("\n")}\n\n`;
3827
+ if (docs.length)
3828
+ output += `### 📝 Documentation\n${docs.map(x => `- ${x}`).join("\n")}\n\n`;
3829
+ if (chore.length)
3830
+ output += `### 🧰 Chores\n${chore.map(x => `- ${x}`).join("\n")}\n\n`;
3831
+ if (others.length)
3832
+ output += `### 📦 General\n${others.map(x => `- ${x}`).join("\n")}\n`;
3833
+ return { content: [{ type: "text", text: output }] };
3834
+ }
3835
+ catch (e) {
3836
+ return { content: [{ type: "text", text: `Changelog generation failed: ${e.message}` }] };
3837
+ }
3838
+ }
3839
+ // ── Spec to Stub ─────────────────────────────────────────────────────
3840
+ async function handleSpecToStub(args) {
3841
+ const filePath = args.filePath;
3842
+ const symbolName = args.symbolName;
3843
+ const workspaceRoot = process.cwd();
3844
+ try {
3845
+ const { resolved } = safeResolvePath(workspaceRoot, filePath);
3846
+ const content = fs.readFileSync(resolved, "utf8");
3847
+ const sourceFile = ts.createSourceFile(resolved, content, ts.ScriptTarget.Latest, true);
3848
+ let stub = null;
3849
+ function visit(node) {
3850
+ if (ts.isInterfaceDeclaration(node) && node.name.text === symbolName) {
3851
+ stub = {};
3852
+ for (const member of node.members) {
3853
+ if (ts.isPropertySignature(member) && member.name) {
3854
+ const nameText = member.name.getText(sourceFile);
3855
+ const typeText = member.type ? member.type.getText(sourceFile) : "any";
3856
+ stub[nameText] = typeText === "number" ? 0 : typeText === "boolean" ? false : typeText === "string" ? '""' : "{}";
3857
+ }
3858
+ }
3859
+ }
3860
+ ts.forEachChild(node, visit);
3861
+ }
3862
+ visit(sourceFile);
3863
+ if (!stub) {
3864
+ return { content: [{ type: "text", text: `Symbol ${symbolName} not found in ${filePath} as a parseable Interface.` }] };
3865
+ }
3866
+ const mockStr = `const mock${symbolName}: ${symbolName} = ${JSON.stringify(stub, null, 2).replace(/"(0|false|""|{})"/g, "$1")};`;
3867
+ return { content: [{ type: "text", text: mockStr }] };
3868
+ }
3869
+ catch (e) {
3870
+ return { content: [{ type: "text", text: `Stub generation failed: ${e.message}` }] };
3871
+ }
3872
+ }
3873
+ // ── Env Snapshot ─────────────────────────────────────────────────────
3874
+ async function handleEnvSnapshot(_args) {
3875
+ const workspaceRoot = process.cwd();
3876
+ try {
3877
+ let nodeVersion = "unknown";
3878
+ try {
3879
+ nodeVersion = execSync("node -v", { encoding: "utf8" }).trim();
3880
+ }
3881
+ catch { }
3882
+ let npmVersion = "unknown";
3883
+ try {
3884
+ npmVersion = execSync("npm -v", { encoding: "utf8" }).trim();
3885
+ }
3886
+ catch { }
3887
+ const pkgPath = path.join(workspaceRoot, "package.json");
3888
+ let deps = {};
3889
+ if (fs.existsSync(pkgPath)) {
3890
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
3891
+ deps = { ...pkg.dependencies, ...pkg.devDependencies };
3892
+ }
3893
+ // Read any .env files, extract only keys
3894
+ const envKeys = [];
3895
+ const envPath = path.join(workspaceRoot, ".env");
3896
+ if (fs.existsSync(envPath)) {
3897
+ const lines = fs.readFileSync(envPath, "utf8").split("\n");
3898
+ lines.forEach((l) => {
3899
+ const match = l.match(/^\s*([A-Za-z0-9_]+)\s*=/);
3900
+ if (match)
3901
+ envKeys.push(match[1]);
3902
+ });
3903
+ }
3904
+ const report = {
3905
+ runtime: { node: nodeVersion, npm: npmVersion },
3906
+ dependencies: deps,
3907
+ envVariablesPresent: envKeys
3908
+ };
3909
+ return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
3910
+ }
3911
+ catch (e) {
3912
+ return { content: [{ type: "text", text: `Environment snapshot failed: ${e.message}` }] };
3913
+ }
3914
+ }
3915
+ // ── Migration Tracer ─────────────────────────────────────────────────
3916
+ async function handleMigrationTracer(args) {
3917
+ const migrationsPath = args.migrationsPath || "prisma/migrations";
3918
+ const workspaceRoot = process.cwd();
3919
+ try {
3920
+ const { resolved } = safeResolvePath(workspaceRoot, migrationsPath);
3921
+ if (!fs.existsSync(resolved)) {
3922
+ return { content: [{ type: "text", text: `Migrations folder not found at ${migrationsPath}` }] };
3923
+ }
3924
+ const subdirs = fs.readdirSync(resolved, { withFileTypes: true })
3925
+ .filter(d => d.isDirectory())
3926
+ .map(d => d.name)
3927
+ .sort();
3928
+ if (subdirs.length === 0) {
3929
+ return { content: [{ type: "text", text: `No migrations found in ${migrationsPath}` }] };
3930
+ }
3931
+ let summary = `Detected ${subdirs.length} migrations in ${migrationsPath}:\n\n`;
3932
+ for (const dir of subdirs.slice(-5)) { // trace the last 5
3933
+ summary += `Migration: ${dir}\n`;
3934
+ const migrationSql = path.join(resolved, dir, "migration.sql");
3935
+ if (fs.existsSync(migrationSql)) {
3936
+ const sql = fs.readFileSync(migrationSql, "utf8");
3937
+ // Extract create table or alter table statements
3938
+ const actions = sql.split("\n")
3939
+ .map(l => l.trim())
3940
+ .filter(l => l.toLowerCase().startsWith("create table") || l.toLowerCase().startsWith("alter table") || l.toLowerCase().startsWith("drop table"));
3941
+ summary += actions.map(act => ` - ${act}`).join("\n") + "\n";
3942
+ }
3943
+ }
3944
+ return { content: [{ type: "text", text: summary }] };
3945
+ }
3946
+ catch (e) {
3947
+ return { content: [{ type: "text", text: `Migration tracing failed: ${e.message}` }] };
3948
+ }
3949
+ }
3950
+ // ── Multi File Patch ─────────────────────────────────────────────────
3951
+ async function handleMultiFilePatch(args) {
3952
+ const patches = args.patches;
3953
+ const dryRun = !!args.dryRun;
3954
+ if (!patches || !Array.isArray(patches)) {
3955
+ throw new Error("patches array is required");
3956
+ }
3957
+ const workspaceRoot = process.cwd();
3958
+ const reports = [];
3959
+ try {
3960
+ for (const patch of patches) {
3961
+ const { resolved } = safeResolvePath(workspaceRoot, patch.filePath);
3962
+ if (!fs.existsSync(resolved)) {
3963
+ reports.push(`❌ ${patch.filePath}: File does not exist`);
3964
+ continue;
3965
+ }
3966
+ const content = fs.readFileSync(resolved, "utf8");
3967
+ const idx = content.indexOf(patch.searchContent);
3968
+ if (idx === -1) {
3969
+ reports.push(`❌ ${patch.filePath}: Search content not found`);
3970
+ continue;
3971
+ }
3972
+ if (content.indexOf(patch.searchContent, idx + 1) !== -1) {
3973
+ reports.push(`❌ ${patch.filePath}: Multiple occurrences of search content`);
3974
+ continue;
3975
+ }
3976
+ if (!dryRun) {
3977
+ const updated = content.replace(patch.searchContent, patch.replaceContent);
3978
+ fs.writeFileSync(resolved, updated, "utf8");
3979
+ reports.push(`✅ ${patch.filePath}: Applied successfully`);
3980
+ }
3981
+ else {
3982
+ reports.push(`🔍 ${patch.filePath}: Match found (dry-run mode)`);
3983
+ }
3984
+ }
3985
+ return { content: [{ type: "text", text: reports.join("\n") }] };
3986
+ }
3987
+ catch (e) {
3988
+ return { content: [{ type: "text", text: `Multi-patch failed: ${e.message}` }] };
3989
+ }
3990
+ }
3991
+ // ── File Intent Index ────────────────────────────────────────────────
3992
+ async function handleFileIntentIndex(args) {
3993
+ const dirPath = args.dirPath || "";
3994
+ const workspaceRoot = process.cwd();
3995
+ try {
3996
+ const { resolved } = safeResolvePath(workspaceRoot, dirPath);
3997
+ const files = listDirRecursive(resolved, dirPath, 1, 4);
3998
+ const categories = {
3999
+ config: [],
4000
+ routing: [],
4001
+ schemas: [],
4002
+ mcp: [],
4003
+ security: [],
4004
+ subconscious: [],
4005
+ others: []
4006
+ };
4007
+ files.forEach((f) => {
4008
+ const lower = f.toLowerCase();
4009
+ if (lower.includes("config") || lower.endsWith(".json") || lower.includes("tsconfig")) {
4010
+ categories.config.push(f);
4011
+ }
4012
+ else if (lower.includes("route") || lower.includes("proxy") || lower.includes("controller")) {
4013
+ categories.routing.push(f);
4014
+ }
4015
+ else if (lower.includes("schema") || lower.includes("type") || lower.includes("model")) {
4016
+ categories.schemas.push(f);
4017
+ }
4018
+ else if (lower.includes("mcp") || lower.includes("tool")) {
4019
+ categories.mcp.push(f);
4020
+ }
4021
+ else if (lower.includes("security") || lower.includes("scrub") || lower.includes("encrypt")) {
4022
+ categories.security.push(f);
4023
+ }
4024
+ else if (lower.includes("subconscious") || lower.includes("brain") || lower.includes("memory")) {
4025
+ categories.subconscious.push(f);
4026
+ }
4027
+ else {
4028
+ categories.others.push(f);
4029
+ }
4030
+ });
4031
+ let summary = `### Workspace File Intent Index:\n\n`;
4032
+ for (const [cat, list] of Object.entries(categories)) {
4033
+ if (list.length > 0) {
4034
+ summary += `📁 **${cat.toUpperCase()}**\n${list.slice(0, 15).map(x => ` - ${x}`).join("\n")}\n\n`;
4035
+ }
4036
+ }
4037
+ return { content: [{ type: "text", text: summary }] };
4038
+ }
4039
+ catch (e) {
4040
+ return { content: [{ type: "text", text: `Intent indexing failed: ${e.message}` }] };
4041
+ }
4042
+ }
4043
+ const COG_MAP_FILE = path.join(os.homedir(), ".lemma-cache", "cog_map.json");
4044
+ async function handleCognitiveMap(args) {
4045
+ const action = args.action;
4046
+ const domain = args.domain;
4047
+ const filePath = args.filePath;
4048
+ const updates = args.updates;
4049
+ const workspaceRoot = process.cwd();
4050
+ // Helper to load map
4051
+ const loadMap = () => {
4052
+ if (fs.existsSync(COG_MAP_FILE)) {
4053
+ try {
4054
+ return JSON.parse(fs.readFileSync(COG_MAP_FILE, "utf8"));
4055
+ }
4056
+ catch { }
4057
+ }
4058
+ return { workspace: path.basename(workspaceRoot), lastBuilt: new Date().toISOString(), nodes: {} };
4059
+ };
4060
+ // Helper to save map
4061
+ const saveMap = (m) => {
4062
+ const dir = path.dirname(COG_MAP_FILE);
4063
+ if (!fs.existsSync(dir))
4064
+ fs.mkdirSync(dir, { recursive: true });
4065
+ fs.writeFileSync(COG_MAP_FILE, JSON.stringify(m, null, 2), "utf8");
4066
+ };
4067
+ const map = loadMap();
4068
+ switch (action) {
4069
+ case "build": {
4070
+ // Automatically scan the workspace recursively to populate the Cognitive Map
4071
+ const files = listDirRecursive(workspaceRoot, "", 1, 3).filter(f => f.endsWith(".ts") || f.endsWith(".js"));
4072
+ const nodes = {};
4073
+ files.forEach((f) => {
4074
+ // Group files by domain intent
4075
+ let dom = "general";
4076
+ if (f.includes("mcp"))
4077
+ dom = "mcp";
4078
+ else if (f.includes("subconscious") || f.includes("brain"))
4079
+ dom = "memory";
4080
+ else if (f.includes("security") || f.includes("scrubber"))
4081
+ dom = "security";
4082
+ else if (f.includes("pr-review"))
4083
+ dom = "pr-review";
4084
+ else if (f.includes("utils"))
4085
+ dom = "utils";
4086
+ if (!nodes[dom]) {
4087
+ nodes[dom] = { domain: dom, files: [], patterns: [], knownBugs: [], decisions: [], symbols: [] };
4088
+ }
4089
+ nodes[dom].files.push(f);
4090
+ // Simple AST parsing to retrieve export symbols
4091
+ try {
4092
+ const fullPath = path.resolve(workspaceRoot, f);
4093
+ const src = fs.readFileSync(fullPath, "utf8");
4094
+ const sf = ts.createSourceFile(fullPath, src, ts.ScriptTarget.Latest, true);
4095
+ ts.forEachChild(sf, (node) => {
4096
+ if ((ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isFunctionDeclaration(node)) && node.name) {
4097
+ nodes[dom].symbols.push(node.name.text);
4098
+ }
4099
+ });
4100
+ }
4101
+ catch { }
4102
+ });
4103
+ // Preserve previously manually enriched patterns/knownBugs/decisions
4104
+ for (const [dom, node] of Object.entries(nodes)) {
4105
+ if (map.nodes[dom]) {
4106
+ node.patterns = [...new Set([...node.patterns, ...map.nodes[dom].patterns])];
4107
+ node.knownBugs = [...new Set([...node.knownBugs, ...map.nodes[dom].knownBugs])];
4108
+ node.decisions = [...new Set([...node.decisions, ...map.nodes[dom].decisions])];
4109
+ }
4110
+ }
4111
+ map.nodes = nodes;
4112
+ map.lastBuilt = new Date().toISOString();
4113
+ saveMap(map);
4114
+ return { content: [{ type: "text", text: `Cognitive Map successfully built with ${Object.keys(nodes).length} domains.` }] };
4115
+ }
4116
+ case "get": {
4117
+ return { content: [{ type: "text", text: JSON.stringify(map, null, 2) }] };
4118
+ }
4119
+ case "query": {
4120
+ if (!domain || !map.nodes[domain]) {
4121
+ return { content: [{ type: "text", text: `Domain "${domain}" not found in Cognitive Map.` }] };
4122
+ }
4123
+ return { content: [{ type: "text", text: JSON.stringify(map.nodes[domain], null, 2) }] };
4124
+ }
4125
+ case "update": {
4126
+ if (!domain)
4127
+ throw new Error("domain is required for update action");
4128
+ if (!map.nodes[domain]) {
4129
+ map.nodes[domain] = { domain, files: [], patterns: [], knownBugs: [], decisions: [], symbols: [] };
4130
+ }
4131
+ const node = map.nodes[domain];
4132
+ if (updates) {
4133
+ if (Array.isArray(updates.patterns))
4134
+ node.patterns = [...new Set([...node.patterns, ...updates.patterns])];
4135
+ if (Array.isArray(updates.knownBugs))
4136
+ node.knownBugs = [...new Set([...node.knownBugs, ...updates.knownBugs])];
4137
+ if (Array.isArray(updates.decisions))
4138
+ node.decisions = [...new Set([...node.decisions, ...updates.decisions])];
4139
+ }
4140
+ saveMap(map);
4141
+ return { content: [{ type: "text", text: `Cognitive Map domain "${domain}" updated successfully.` }] };
4142
+ }
4143
+ case "file_context": {
4144
+ if (!filePath)
4145
+ throw new Error("filePath is required for file_context action");
4146
+ const norm = filePath.replace(/^\//, "");
4147
+ let foundDomain = null;
4148
+ for (const node of Object.values(map.nodes)) {
4149
+ if (node.files.includes(norm)) {
4150
+ foundDomain = node;
4151
+ break;
4152
+ }
4153
+ }
4154
+ if (!foundDomain) {
4155
+ return { content: [{ type: "text", text: `No specific domain found for file "${filePath}".` }] };
4156
+ }
4157
+ return { content: [{ type: "text", text: JSON.stringify(foundDomain, null, 2) }] };
4158
+ }
4159
+ default:
4160
+ throw new Error(`Unknown action: ${action}`);
4161
+ }
4162
+ }
4163
+ // ── Semantic Grep ───────────────────────────────────────────────────
4164
+ async function handleSemanticGrep(args) {
4165
+ const query = args.query;
4166
+ const dirPath = args.dirPath || "";
4167
+ const limit = typeof args.limit === "number" ? args.limit : 5;
4168
+ const workspaceRoot = process.cwd();
4169
+ try {
4170
+ const { resolved } = safeResolvePath(workspaceRoot, dirPath);
4171
+ const files = listDirRecursive(resolved, dirPath, 1, 4)
4172
+ .filter(f => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".json") || f.endsWith(".md"));
4173
+ const queryTokens = query.toLowerCase()
4174
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
4175
+ .replace(/[_\-]/g, ' ')
4176
+ .replace(/[^\w\s]/g, ' ')
4177
+ .split(/\s+/)
4178
+ .filter(t => t.length >= 2);
4179
+ if (queryTokens.length === 0) {
4180
+ return { content: [{ type: "text", text: "Query does not contain searchable tokens." }] };
4181
+ }
4182
+ const scoredFiles = [];
4183
+ files.forEach((f) => {
4184
+ try {
4185
+ const fullPath = path.resolve(workspaceRoot, f);
4186
+ const content = fs.readFileSync(fullPath, "utf8");
4187
+ const lines = content.split("\n");
4188
+ const fileLower = content.toLowerCase();
4189
+ let tokenMatches = 0;
4190
+ queryTokens.forEach(t => {
4191
+ if (fileLower.includes(t))
4192
+ tokenMatches++;
4193
+ });
4194
+ if (tokenMatches > 0) {
4195
+ const matchedLines = [];
4196
+ lines.forEach((line, idx) => {
4197
+ const lineLower = line.toLowerCase();
4198
+ const matchesQuery = queryTokens.some(t => lineLower.includes(t));
4199
+ if (matchesQuery && matchedLines.length < 3) {
4200
+ matchedLines.push(` L${idx + 1}: ${line.trim()}`);
4201
+ }
4202
+ });
4203
+ // simple score calculation based on percentage match + file hits
4204
+ const score = tokenMatches / queryTokens.length;
4205
+ scoredFiles.push({ filePath: f, score, matchedLines });
4206
+ }
4207
+ }
4208
+ catch { }
4209
+ });
4210
+ // Sort by score desc
4211
+ scoredFiles.sort((a, b) => b.score - a.score);
4212
+ const topMatches = scoredFiles.slice(0, limit);
4213
+ if (topMatches.length === 0) {
4214
+ return { content: [{ type: "text", text: `No relevant files found matching concept: "${query}"` }] };
4215
+ }
4216
+ let summary = `### Semantic Grep matches for "${query}":\n\n`;
4217
+ topMatches.forEach((m, idx) => {
4218
+ summary += `🎯 **[${idx + 1}] ${m.filePath}** (Relevance: ${(m.score * 100).toFixed(0)}%)\n`;
4219
+ summary += m.matchedLines.join("\n") + "\n\n";
4220
+ });
4221
+ return { content: [{ type: "text", text: summary }] };
4222
+ }
4223
+ catch (e) {
4224
+ return { content: [{ type: "text", text: `Semantic grep failed: ${e.message}` }] };
4225
+ }
4226
+ }
4227
+ // ── Imports Skeleton Resolver ───────────────────────────────────────
4228
+ async function handleImportsSkeletonResolver(args) {
4229
+ const filePath = args.filePath;
4230
+ const workspaceRoot = process.cwd();
4231
+ try {
4232
+ const { resolved } = safeResolvePath(workspaceRoot, filePath);
4233
+ const content = fs.readFileSync(resolved, "utf8");
4234
+ const sourceFile = ts.createSourceFile(resolved, content, ts.ScriptTarget.Latest, true);
4235
+ const importDeclarations = [];
4236
+ ts.forEachChild(sourceFile, (node) => {
4237
+ if (ts.isImportDeclaration(node)) {
4238
+ importDeclarations.push(node);
4239
+ }
4240
+ });
4241
+ if (importDeclarations.length === 0) {
4242
+ return { content: [{ type: "text", text: `No import statements found in ${filePath}.` }] };
4243
+ }
4244
+ let output = `# Import reference signatures for \`${filePath}\`:\n\n`;
4245
+ let foundImportsCount = 0;
4246
+ for (const imp of importDeclarations) {
4247
+ const moduleSpec = imp.moduleSpecifier.getText(sourceFile).replace(/['"]/g, "");
4248
+ // Skip node_modules/external library imports
4249
+ if (moduleSpec.startsWith(".") || moduleSpec.startsWith("/")) {
4250
+ foundImportsCount++;
4251
+ const targetPath = path.resolve(path.dirname(resolved), moduleSpec);
4252
+ // Find matching file path with ts/js extensions
4253
+ let matchPath = "";
4254
+ const extensions = [".ts", ".tsx", ".d.ts", ".js", ".jsx"];
4255
+ if (fs.existsSync(targetPath) && fs.statSync(targetPath).isFile()) {
4256
+ matchPath = targetPath;
4257
+ }
4258
+ else {
4259
+ for (const ext of extensions) {
4260
+ if (fs.existsSync(targetPath + ext)) {
4261
+ matchPath = targetPath + ext;
4262
+ break;
4263
+ }
4264
+ }
4265
+ }
4266
+ if (matchPath) {
4267
+ const relMatchPath = path.relative(workspaceRoot, matchPath);
4268
+ output += `## 📦 Signatures from: \`${relMatchPath}\` (imported via \`${moduleSpec}\`)\n`;
4269
+ try {
4270
+ const importContent = fs.readFileSync(matchPath, "utf8");
4271
+ const importSourceFile = ts.createSourceFile(matchPath, importContent, ts.ScriptTarget.Latest, true);
4272
+ const exportedSignatures = [];
4273
+ // Simple AST traverse to collect exported signatures
4274
+ ts.forEachChild(importSourceFile, (node) => {
4275
+ const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
4276
+ const isExported = mods?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
4277
+ if (isExported) {
4278
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
4279
+ const printer = ts.createPrinter({ removeComments: true });
4280
+ // Strip method body implementation code where applicable
4281
+ const printed = printer.printNode(ts.EmitHint.Unspecified, node, importSourceFile);
4282
+ exportedSignatures.push(printed);
4283
+ }
4284
+ else if (ts.isFunctionDeclaration(node)) {
4285
+ // Print just the header/signature of the function
4286
+ const printer = ts.createPrinter({ removeComments: true });
4287
+ const printed = printer.printNode(ts.EmitHint.Unspecified, node, importSourceFile);
4288
+ // Quick strip of function implementation body if present
4289
+ const bodyIndex = printed.indexOf(" {");
4290
+ if (bodyIndex !== -1) {
4291
+ exportedSignatures.push(printed.substring(0, bodyIndex) + ";");
4292
+ }
4293
+ else {
4294
+ exportedSignatures.push(printed);
4295
+ }
4296
+ }
4297
+ }
4298
+ });
4299
+ if (exportedSignatures.length > 0) {
4300
+ output += "```typescript\n" + exportedSignatures.join("\n\n") + "\n```\n\n";
4301
+ }
4302
+ else {
4303
+ output += "*No public exported symbols found in this file.*\n\n";
4304
+ }
4305
+ }
4306
+ catch (e) {
4307
+ output += `*Failed to parse signatures: ${e.message}*\n\n`;
4308
+ }
4309
+ }
4310
+ else {
4311
+ output += `## 📦 Signatures from: \`${moduleSpec}\`\n*Local source file not found.*\n\n`;
4312
+ }
4313
+ }
4314
+ }
4315
+ if (foundImportsCount === 0) {
4316
+ return { content: [{ type: "text", text: "No local imports detected." }] };
4317
+ }
4318
+ return { content: [{ type: "text", text: output }] };
4319
+ }
4320
+ catch (e) {
4321
+ return { content: [{ type: "text", text: `Import resolution failed: ${e.message}` }] };
4322
+ }
4323
+ }
4324
+ // ── AST Flow Visualizer ──────────────────────────────────────────────
4325
+ async function handleAstFlowVisualizer(args) {
4326
+ const filePath = args.filePath;
4327
+ const functionName = args.functionName;
4328
+ const workspaceRoot = process.cwd();
4329
+ try {
4330
+ const { resolved } = safeResolvePath(workspaceRoot, filePath);
4331
+ const content = fs.readFileSync(resolved, "utf8");
4332
+ const sourceFile = ts.createSourceFile(resolved, content, ts.ScriptTarget.Latest, true);
4333
+ let targetNode = null;
4334
+ function findFunction(node) {
4335
+ const nameText = (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && node.name ? node.name.getText(sourceFile) : "";
4336
+ if (nameText === functionName) {
4337
+ targetNode = node;
4338
+ }
4339
+ else if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name) && node.name.text === functionName) {
4340
+ if (node.initializer && (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
4341
+ targetNode = node.initializer;
4342
+ }
4343
+ }
4344
+ if (!targetNode) {
4345
+ ts.forEachChild(node, findFunction);
4346
+ }
4347
+ }
4348
+ findFunction(sourceFile);
4349
+ if (!targetNode) {
4350
+ return { content: [{ type: "text", text: `Function/Method "${functionName}" not found in file ${filePath}.` }] };
4351
+ }
4352
+ const bodyNode = targetNode.body;
4353
+ if (!bodyNode) {
4354
+ return { content: [{ type: "text", text: `Function "${functionName}" has no parseable implementation body.` }] };
4355
+ }
4356
+ // Traversal state
4357
+ let mermaid = "flowchart TD\n";
4358
+ let nodeId = 0;
4359
+ function getNextId() {
4360
+ return `node_${nodeId++}`;
4361
+ }
4362
+ function formatLabel(text) {
4363
+ return text.trim()
4364
+ .replace(/"/g, "'")
4365
+ .replace(/[{}()]/g, "")
4366
+ .substring(0, 55);
4367
+ }
4368
+ function traverse(n, parentId) {
4369
+ if (ts.isIfStatement(n)) {
4370
+ const condId = getNextId();
4371
+ const condText = formatLabel(n.expression.getText(sourceFile));
4372
+ mermaid += ` ${parentId} --> ${condId}{"if ${condText}"}\n`;
4373
+ const thenId = getNextId();
4374
+ mermaid += ` ${condId} -->|true| ${thenId}["then block"]\n`;
4375
+ const lastThen = traverse(n.thenStatement, thenId);
4376
+ const joinId = getNextId();
4377
+ mermaid += ` ${joinId}(("join"))\n`;
4378
+ mermaid += ` ${lastThen} --> ${joinId}\n`;
4379
+ if (n.elseStatement) {
4380
+ const elseId = getNextId();
4381
+ mermaid += ` ${condId} -->|false| ${elseId}["else block"]\n`;
4382
+ const lastElse = traverse(n.elseStatement, elseId);
4383
+ mermaid += ` ${lastElse} --> ${joinId}\n`;
4384
+ }
4385
+ else {
4386
+ mermaid += ` ${condId} -->|false| ${joinId}\n`;
4387
+ }
4388
+ return joinId;
4389
+ }
4390
+ if (ts.isTryStatement(n)) {
4391
+ const tryId = getNextId();
4392
+ mermaid += ` ${parentId} --> ${tryId}["try block"]\n`;
4393
+ const lastTry = traverse(n.tryBlock, tryId);
4394
+ const joinId = getNextId();
4395
+ mermaid += ` ${joinId}(("join"))\n`;
4396
+ mermaid += ` ${lastTry} --> ${joinId}\n`;
4397
+ if (n.catchClause) {
4398
+ const catchId = getNextId();
4399
+ mermaid += ` ${parentId} -.->|on error| ${catchId}["catch block"]\n`;
4400
+ const lastCatch = traverse(n.catchClause.block, catchId);
4401
+ mermaid += ` ${lastCatch} --> ${joinId}\n`;
4402
+ }
4403
+ return joinId;
4404
+ }
4405
+ if (ts.isForStatement(n) || ts.isForOfStatement(n) || ts.isForInStatement(n) || ts.isWhileStatement(n)) {
4406
+ const loopId = getNextId();
4407
+ const condText = ts.isWhileStatement(n)
4408
+ ? formatLabel(n.expression.getText(sourceFile))
4409
+ : "loop condition";
4410
+ mermaid += ` ${parentId} --> ${loopId}{"loop: ${condText}"}\n`;
4411
+ const bodyId = getNextId();
4412
+ mermaid += ` ${loopId} -->|iterate| ${bodyId}["loop body"]\n`;
4413
+ const lastBody = traverse(n.statement, bodyId);
4414
+ mermaid += ` ${lastBody} --> ${loopId}\n`;
4415
+ const endId = getNextId();
4416
+ mermaid += ` ${loopId} -->|done| ${endId}(("end loop"))\n`;
4417
+ return endId;
4418
+ }
4419
+ if (ts.isBlock(n)) {
4420
+ let currentParent = parentId;
4421
+ n.statements.forEach((stmt) => {
4422
+ currentParent = traverse(stmt, currentParent);
4423
+ });
4424
+ return currentParent;
4425
+ }
4426
+ const text = formatLabel(n.getText(sourceFile));
4427
+ if (text.length > 0) {
4428
+ const id = getNextId();
4429
+ mermaid += ` ${parentId} --> ${id}["${text}"]\n`;
4430
+ return id;
4431
+ }
4432
+ return parentId;
4433
+ }
4434
+ const startId = getNextId();
4435
+ mermaid += ` ${startId}(("Start: ${functionName}"))\n`;
4436
+ traverse(bodyNode, startId);
4437
+ const wrappedMermaid = `\`\`\`mermaid\n${mermaid}\`\`\``;
4438
+ return { content: [{ type: "text", text: `### Visual AST Flowchart for \`${functionName}\` inside \`${filePath}\`:\n\n${wrappedMermaid}` }] };
4439
+ }
4440
+ catch (e) {
4441
+ return { content: [{ type: "text", text: `AST flow visualization failed: ${e.message}` }] };
4442
+ }
4443
+ }
3469
4444
  //# sourceMappingURL=tools.js.map