@nxuss/lemma 1.0.1 → 1.0.3

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