shark-ai 0.3.17 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/shark.js CHANGED
@@ -342,6 +342,20 @@ var SSEClient = class {
342
342
  if (!response.body) {
343
343
  throw new Error("Response body is null");
344
344
  }
345
+ const contentType = response.headers.get("content-type") || "";
346
+ const isJson = contentType.includes("application/json");
347
+ if (isJson) {
348
+ const jsonBody = await response.json();
349
+ FileLogger.log("SSE", "Received Non-Streaming JSON Response", { length: JSON.stringify(jsonBody).length });
350
+ let content = "";
351
+ if (typeof jsonBody === "string") content = jsonBody;
352
+ else if (jsonBody.message) content = jsonBody.message;
353
+ else if (jsonBody.choices?.[0]?.message?.content) content = jsonBody.choices[0].message.content;
354
+ else content = JSON.stringify(jsonBody);
355
+ if (onChunk) onChunk(content);
356
+ if (onComplete) onComplete(content, jsonBody);
357
+ return;
358
+ }
345
359
  const reader = response.body.getReader();
346
360
  const decoder = new TextDecoder();
347
361
  let buffer = "";
@@ -403,7 +417,35 @@ var sseClient = new SSEClient();
403
417
  // src/core/agents/agent-response-parser.ts
404
418
  import { z as z2 } from "zod";
405
419
  var AgentActionSchema = z2.object({
406
- type: z2.enum(["create_file", "modify_file", "modify_ast", "search_ast", "delete_file", "talk_with_user", "list_files", "read_file", "search_file", "run_command", "use_mcp_tool"]),
420
+ type: z2.enum([
421
+ "create_file",
422
+ "modify_file",
423
+ "modify_ast",
424
+ "search_ast",
425
+ "delete_file",
426
+ "talk_with_user",
427
+ "list_files",
428
+ "read_file",
429
+ "search_file",
430
+ "run_command",
431
+ "use_mcp_tool",
432
+ // New AST Tools
433
+ "ast_list_structure",
434
+ "ast_add_method",
435
+ "ast_modify_method",
436
+ "ast_remove_method",
437
+ "ast_add_class",
438
+ "ast_add_property",
439
+ "ast_remove_property",
440
+ "ast_add_decorator",
441
+ "ast_add_interface",
442
+ "ast_add_type_alias",
443
+ "ast_add_function",
444
+ "ast_remove_function",
445
+ "ast_add_import",
446
+ "ast_remove_import",
447
+ "ast_organize_imports"
448
+ ]),
407
449
  path: z2.string().nullable().optional(),
408
450
  // Nullable for strict mode combatibility
409
451
  content: z2.string().nullable().optional(),
@@ -419,6 +461,22 @@ var AgentActionSchema = z2.object({
419
461
  language: z2.string().nullable().optional(),
420
462
  file_path: z2.string().nullable().optional(),
421
463
  // Alias for path in ast-grep actions
464
+ // New AST Tool Specific Fields
465
+ class_name: z2.string().nullable().optional(),
466
+ method_name: z2.string().nullable().optional(),
467
+ method_code: z2.string().nullable().optional(),
468
+ property_name: z2.string().nullable().optional(),
469
+ property_code: z2.string().nullable().optional(),
470
+ extends_class: z2.string().nullable().optional(),
471
+ implements_interfaces: z2.array(z2.string()).nullable().optional(),
472
+ decorator_code: z2.string().nullable().optional(),
473
+ interface_code: z2.string().nullable().optional(),
474
+ type_code: z2.string().nullable().optional(),
475
+ function_name: z2.string().nullable().optional(),
476
+ function_code: z2.string().nullable().optional(),
477
+ import_statement: z2.string().nullable().optional(),
478
+ module_path: z2.string().nullable().optional(),
479
+ new_body: z2.string().nullable().optional(),
422
480
  // Preview confirmation
423
481
  confirmed: z2.boolean().nullable().optional()
424
482
  });
@@ -772,19 +830,404 @@ async function interactiveBusinessAnalyst() {
772
830
  }
773
831
 
774
832
  // src/core/agents/specification-agent.ts
775
- import fs4 from "fs";
776
- import path5 from "path";
833
+ import fs5 from "fs";
834
+ import path7 from "path";
777
835
 
778
836
  // src/core/agents/agent-tools.ts
779
- import fs3 from "fs";
780
- import path4 from "path";
837
+ import fs4 from "fs";
838
+ import path6 from "path";
781
839
  import fg from "fast-glob";
782
840
  import { exec } from "child_process";
783
841
  import { promisify } from "util";
784
842
  import { fileURLToPath } from "url";
785
843
 
844
+ // src/core/ast-editing/editors/code-editor-factory.ts
845
+ import * as path4 from "path";
846
+
847
+ // src/core/ast-editing/editors/typescript-editor.ts
848
+ import { Project, SyntaxKind } from "ts-morph";
849
+ import * as path3 from "path";
850
+ import * as fs3 from "fs";
851
+ var TypeScriptEditor = class {
852
+ project;
853
+ constructor() {
854
+ this.project = new Project({
855
+ skipAddingFilesFromTsConfig: true,
856
+ compilerOptions: {
857
+ target: 99,
858
+ // ESNext
859
+ module: 99
860
+ // ESNext
861
+ }
862
+ });
863
+ }
864
+ /**
865
+ * Get or add source file to project
866
+ */
867
+ getSourceFile(filePath) {
868
+ const absolutePath = path3.resolve(filePath);
869
+ let sourceFile = this.project.getSourceFile(absolutePath);
870
+ if (!sourceFile) {
871
+ if (!fs3.existsSync(absolutePath)) {
872
+ throw new Error(`File not found: ${absolutePath}`);
873
+ }
874
+ sourceFile = this.project.addSourceFileAtPath(absolutePath);
875
+ }
876
+ return sourceFile;
877
+ }
878
+ /**
879
+ * Get class declaration by name
880
+ */
881
+ getClass(sourceFile, className) {
882
+ const classDecl = sourceFile.getClass(className);
883
+ if (!classDecl) {
884
+ throw new Error(`Class "${className}" not found in ${sourceFile.getFilePath()}`);
885
+ }
886
+ return classDecl;
887
+ }
888
+ // ═══════════════════════════════════════════════════════
889
+ // IMPLEMENTATION: listStructure
890
+ // ═══════════════════════════════════════════════════════
891
+ async listStructure(filePath) {
892
+ const sourceFile = this.getSourceFile(filePath);
893
+ const classes = sourceFile.getClasses().map((cls) => ({
894
+ name: cls.getName() || "<anonymous>",
895
+ methods: cls.getMethods().map((m) => this.extractMethodInfo(m)),
896
+ properties: cls.getProperties().map((p) => this.extractPropertyInfo(p)),
897
+ decorators: cls.getDecorators().map((d) => d.getText()),
898
+ extendsClass: cls.getExtends()?.getText(),
899
+ implementsInterfaces: cls.getImplements().map((i) => i.getText())
900
+ }));
901
+ const interfaces = sourceFile.getInterfaces().map((iface) => ({
902
+ name: iface.getName(),
903
+ properties: iface.getProperties().map((p) => this.extractPropertyInfo(p)),
904
+ extends: iface.getExtends().map((e) => e.getText())
905
+ }));
906
+ const functions = sourceFile.getFunctions().map((fn) => ({
907
+ name: fn.getName() || "<anonymous>",
908
+ parameters: fn.getParameters().map((p) => ({
909
+ name: p.getName(),
910
+ type: p.getType().getText(),
911
+ isOptional: p.isOptional()
912
+ })),
913
+ returnType: fn.getReturnType().getText(),
914
+ isAsync: fn.isAsync(),
915
+ isExported: fn.isExported()
916
+ }));
917
+ const imports = sourceFile.getImportDeclarations().map((imp) => ({
918
+ modulePath: imp.getModuleSpecifierValue(),
919
+ isDefault: !!imp.getDefaultImport(),
920
+ namedImports: imp.getNamedImports().map((n) => n.getName()),
921
+ namespaceImport: imp.getNamespaceImport()?.getText()
922
+ }));
923
+ const exports = sourceFile.getExportedDeclarations();
924
+ const exportInfo = Array.from(exports.entries()).flatMap(
925
+ ([name, declarations]) => declarations.map((decl) => ({
926
+ name,
927
+ type: this.getDeclarationType(decl),
928
+ isDefault: sourceFile.getDefaultExportSymbol()?.getName() === name
929
+ }))
930
+ );
931
+ return {
932
+ classes,
933
+ interfaces,
934
+ functions,
935
+ imports,
936
+ exports: exportInfo
937
+ };
938
+ }
939
+ extractMethodInfo(method) {
940
+ return {
941
+ name: method.getName(),
942
+ parameters: method.getParameters().map((p) => ({
943
+ name: p.getName(),
944
+ type: p.getType().getText(),
945
+ isOptional: p.isOptional()
946
+ })),
947
+ returnType: method.getReturnType().getText(),
948
+ isAsync: method.isAsync(),
949
+ isStatic: method.isStatic(),
950
+ visibility: this.getVisibility(method),
951
+ decorators: method.getDecorators().map((d) => d.getText())
952
+ };
953
+ }
954
+ extractPropertyInfo(property) {
955
+ return {
956
+ name: property.getName(),
957
+ type: property.getType()?.getText(),
958
+ visibility: this.getVisibility(property),
959
+ isReadonly: property.isReadonly(),
960
+ initializer: property.getInitializer()?.getText()
961
+ };
962
+ }
963
+ getVisibility(node) {
964
+ if (node.hasModifier?.(SyntaxKind.PrivateKeyword)) return "private";
965
+ if (node.hasModifier?.(SyntaxKind.ProtectedKeyword)) return "protected";
966
+ return "public";
967
+ }
968
+ getDeclarationType(decl) {
969
+ if (decl.getKind() === SyntaxKind.ClassDeclaration) return "class";
970
+ if (decl.getKind() === SyntaxKind.FunctionDeclaration) return "function";
971
+ if (decl.getKind() === SyntaxKind.InterfaceDeclaration) return "interface";
972
+ if (decl.getKind() === SyntaxKind.TypeAliasDeclaration) return "type";
973
+ return "const";
974
+ }
975
+ // ═══════════════════════════════════════════════════════
976
+ // IMPLEMENTATION: Class Operations
977
+ // ═══════════════════════════════════════════════════════
978
+ async addClass(filePath, className, options) {
979
+ try {
980
+ const sourceFile = this.getSourceFile(filePath);
981
+ sourceFile.addClass({
982
+ name: className,
983
+ extends: options?.extendsClass,
984
+ implements: options?.implementsInterfaces,
985
+ isExported: true
986
+ });
987
+ await sourceFile.save();
988
+ return true;
989
+ } catch (error) {
990
+ console.error(`Failed to add class "${className}":`, error);
991
+ return false;
992
+ }
993
+ }
994
+ async addProperty(filePath, className, propertyCode) {
995
+ try {
996
+ const sourceFile = this.getSourceFile(filePath);
997
+ const classDecl = this.getClass(sourceFile, className);
998
+ const closeBrace = classDecl.getEnd() - 1;
999
+ classDecl.insertText(closeBrace, `
1000
+ ${propertyCode}`);
1001
+ classDecl.formatText();
1002
+ await sourceFile.save();
1003
+ return true;
1004
+ } catch (error) {
1005
+ console.error(`Failed to add property to "${className}":`, error);
1006
+ return false;
1007
+ }
1008
+ }
1009
+ async removeProperty(filePath, className, propertyName) {
1010
+ try {
1011
+ const sourceFile = this.getSourceFile(filePath);
1012
+ const classDecl = this.getClass(sourceFile, className);
1013
+ const property = classDecl.getProperty(propertyName);
1014
+ if (!property) {
1015
+ throw new Error(`Property "${propertyName}" not found in class "${className}"`);
1016
+ }
1017
+ property.remove();
1018
+ await sourceFile.save();
1019
+ return true;
1020
+ } catch (error) {
1021
+ console.error(`Failed to remove property "${propertyName}":`, error);
1022
+ return false;
1023
+ }
1024
+ }
1025
+ async addMethod(filePath, className, methodCode) {
1026
+ try {
1027
+ const sourceFile = this.getSourceFile(filePath);
1028
+ const classDecl = this.getClass(sourceFile, className);
1029
+ const closeBrace = classDecl.getEnd() - 1;
1030
+ classDecl.insertText(closeBrace, `
1031
+ ${methodCode}
1032
+ `);
1033
+ classDecl.formatText();
1034
+ await sourceFile.save();
1035
+ return true;
1036
+ } catch (error) {
1037
+ console.error(`Failed to add method to "${className}":`, error);
1038
+ return false;
1039
+ }
1040
+ }
1041
+ async modifyMethod(filePath, className, methodName, newBody) {
1042
+ try {
1043
+ const sourceFile = this.getSourceFile(filePath);
1044
+ const classDecl = this.getClass(sourceFile, className);
1045
+ const method = classDecl.getMethod(methodName);
1046
+ if (!method) {
1047
+ throw new Error(`Method "${methodName}" not found in class "${className}"`);
1048
+ }
1049
+ method.setBodyText(newBody);
1050
+ method.formatText();
1051
+ await sourceFile.save();
1052
+ return true;
1053
+ } catch (error) {
1054
+ console.error(`Failed to modify method "${methodName}":`, error);
1055
+ return false;
1056
+ }
1057
+ }
1058
+ async removeMethod(filePath, className, methodName) {
1059
+ try {
1060
+ const sourceFile = this.getSourceFile(filePath);
1061
+ const classDecl = this.getClass(sourceFile, className);
1062
+ const method = classDecl.getMethod(methodName);
1063
+ if (!method) {
1064
+ throw new Error(`Method "${methodName}" not found in class "${className}"`);
1065
+ }
1066
+ method.remove();
1067
+ await sourceFile.save();
1068
+ return true;
1069
+ } catch (error) {
1070
+ console.error(`Failed to remove method "${methodName}":`, error);
1071
+ return false;
1072
+ }
1073
+ }
1074
+ async addDecorator(filePath, className, decoratorCode) {
1075
+ try {
1076
+ const sourceFile = this.getSourceFile(filePath);
1077
+ const classDecl = this.getClass(sourceFile, className);
1078
+ const start = classDecl.getStart();
1079
+ sourceFile.insertText(start, `${decoratorCode}
1080
+ `);
1081
+ sourceFile.formatText();
1082
+ await sourceFile.save();
1083
+ return true;
1084
+ } catch (error) {
1085
+ console.error(`Failed to add decorator to "${className}":`, error);
1086
+ return false;
1087
+ }
1088
+ }
1089
+ // ═══════════════════════════════════════════════════════
1090
+ // IMPLEMENTATION: Interface/Type Operations
1091
+ // ═══════════════════════════════════════════════════════
1092
+ async addInterface(filePath, interfaceCode) {
1093
+ try {
1094
+ const sourceFile = this.getSourceFile(filePath);
1095
+ sourceFile.insertText(sourceFile.getEnd(), `
1096
+
1097
+ ${interfaceCode}`);
1098
+ sourceFile.formatText();
1099
+ await sourceFile.save();
1100
+ return true;
1101
+ } catch (error) {
1102
+ console.error(`Failed to add interface:`, error);
1103
+ return false;
1104
+ }
1105
+ }
1106
+ async addTypeAlias(filePath, typeCode) {
1107
+ try {
1108
+ const sourceFile = this.getSourceFile(filePath);
1109
+ sourceFile.insertText(sourceFile.getEnd(), `
1110
+
1111
+ ${typeCode}`);
1112
+ sourceFile.formatText();
1113
+ await sourceFile.save();
1114
+ return true;
1115
+ } catch (error) {
1116
+ console.error(`Failed to add type alias:`, error);
1117
+ return false;
1118
+ }
1119
+ }
1120
+ // ═══════════════════════════════════════════════════════
1121
+ // IMPLEMENTATION: Function Operations
1122
+ // ═══════════════════════════════════════════════════════
1123
+ async addFunction(filePath, functionCode) {
1124
+ try {
1125
+ const sourceFile = this.getSourceFile(filePath);
1126
+ sourceFile.insertText(sourceFile.getEnd(), `
1127
+
1128
+ ${functionCode}`);
1129
+ sourceFile.formatText();
1130
+ await sourceFile.save();
1131
+ return true;
1132
+ } catch (error) {
1133
+ console.error(`Failed to add function:`, error);
1134
+ return false;
1135
+ }
1136
+ }
1137
+ async removeFunction(filePath, functionName) {
1138
+ try {
1139
+ const sourceFile = this.getSourceFile(filePath);
1140
+ const func = sourceFile.getFunction(functionName);
1141
+ if (!func) {
1142
+ throw new Error(`Function "${functionName}" not found`);
1143
+ }
1144
+ func.remove();
1145
+ await sourceFile.save();
1146
+ return true;
1147
+ } catch (error) {
1148
+ console.error(`Failed to remove function "${functionName}":`, error);
1149
+ return false;
1150
+ }
1151
+ }
1152
+ // ═══════════════════════════════════════════════════════
1153
+ // IMPLEMENTATION: Import/Export Operations
1154
+ // ═══════════════════════════════════════════════════════
1155
+ async addImport(filePath, importStatement) {
1156
+ try {
1157
+ const sourceFile = this.getSourceFile(filePath);
1158
+ const lastImport = sourceFile.getImportDeclarations().pop();
1159
+ const pos = lastImport ? lastImport.getEnd() : 0;
1160
+ sourceFile.insertText(pos, `
1161
+ ${importStatement}`);
1162
+ this.organizeImports(filePath);
1163
+ await sourceFile.save();
1164
+ return true;
1165
+ } catch (error) {
1166
+ console.error(`Failed to add import:`, error);
1167
+ return false;
1168
+ }
1169
+ }
1170
+ async removeImport(filePath, modulePath) {
1171
+ try {
1172
+ const sourceFile = this.getSourceFile(filePath);
1173
+ const importDecls = sourceFile.getImportDeclarations().filter((imp) => imp.getModuleSpecifierValue() === modulePath);
1174
+ if (importDecls.length === 0) {
1175
+ throw new Error(`Import from "${modulePath}" not found`);
1176
+ }
1177
+ importDecls.forEach((d) => d.remove());
1178
+ await sourceFile.save();
1179
+ return true;
1180
+ } catch (error) {
1181
+ console.error(`Failed to remove import from "${modulePath}":`, error);
1182
+ return false;
1183
+ }
1184
+ }
1185
+ async organizeImports(filePath) {
1186
+ try {
1187
+ const sourceFile = this.getSourceFile(filePath);
1188
+ sourceFile.organizeImports();
1189
+ const imports = sourceFile.getImportDeclarations();
1190
+ const importStructure = imports.map((i) => i.getStructure());
1191
+ importStructure.sort((a, b) => {
1192
+ return a.moduleSpecifier.localeCompare(b.moduleSpecifier);
1193
+ });
1194
+ imports.forEach((i) => i.remove());
1195
+ sourceFile.addImportDeclarations(importStructure);
1196
+ await sourceFile.save();
1197
+ return true;
1198
+ } catch (error) {
1199
+ console.error(`Failed to organize imports:`, error);
1200
+ return false;
1201
+ }
1202
+ }
1203
+ };
1204
+
1205
+ // src/core/ast-editing/editors/code-editor-factory.ts
1206
+ var CodeEditorFactory = class {
1207
+ static tsEditor = null;
1208
+ /**
1209
+ * Returns appropriate editor for the file, or null if AST editing not supported
1210
+ */
1211
+ static getEditor(filePath) {
1212
+ const ext = path4.extname(filePath).toLowerCase();
1213
+ if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
1214
+ if (!this.tsEditor) {
1215
+ this.tsEditor = new TypeScriptEditor();
1216
+ }
1217
+ return this.tsEditor;
1218
+ }
1219
+ return null;
1220
+ }
1221
+ /**
1222
+ * Clear cached editors (useful for testing)
1223
+ */
1224
+ static clearCache() {
1225
+ this.tsEditor = null;
1226
+ }
1227
+ };
1228
+
786
1229
  // src/core/services/code-review.service.ts
787
- import path3 from "path";
1230
+ import path5 from "path";
788
1231
  var CodeReviewService = class {
789
1232
  /**
790
1233
  * Reviews the code modification and returns a status/feedback string.
@@ -793,7 +1236,7 @@ var CodeReviewService = class {
793
1236
  static async reviewCode(filePath, newContent) {
794
1237
  const config = ConfigManager.getInstance().getConfig();
795
1238
  const validation = config.validation || { llmReviewExtensions: [".ts", ".tsx"], syntaxCheckExtensions: ["*"] };
796
- const ext = path3.extname(filePath).toLowerCase();
1239
+ const ext = path5.extname(filePath).toLowerCase();
797
1240
  const needsLlmReview = validation.llmReviewExtensions.includes(ext) || validation.llmReviewExtensions.includes("*");
798
1241
  if (needsLlmReview) {
799
1242
  return await this.performLlmReview(filePath, newContent);
@@ -818,7 +1261,7 @@ var CodeReviewService = class {
818
1261
  try {
819
1262
  const realm = await getActiveRealm();
820
1263
  const token = await ensureValidToken(realm);
821
- const prompt = `You are a Code Review Specialist. Analyze the following code modification for file: ${path3.basename(filePath)}
1264
+ const prompt = `You are a Code Review Specialist. Analyze the following code modification for file: ${path5.basename(filePath)}
822
1265
 
823
1266
  NEW CODE TO BE ADDED:
824
1267
  \`\`\`
@@ -833,8 +1276,8 @@ Provide a concise review focusing on:
833
1276
  Respond in JSON format with status, issues array, and summary.`;
834
1277
  const payload = {
835
1278
  user_prompt: prompt,
836
- streaming: false,
837
- use_conversation: false,
1279
+ streaming: true,
1280
+ use_conversation: true,
838
1281
  stackspot_knowledge: false
839
1282
  };
840
1283
  const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${agentId}/chat`;
@@ -920,9 +1363,9 @@ function detectLineEnding(content) {
920
1363
  }
921
1364
  function handleListFiles(dirPath) {
922
1365
  try {
923
- const fullPath = path4.resolve(process.cwd(), dirPath);
924
- if (!fs3.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
925
- const items = fs3.readdirSync(fullPath, { withFileTypes: true });
1366
+ const fullPath = path6.resolve(process.cwd(), dirPath);
1367
+ if (!fs4.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
1368
+ const items = fs4.readdirSync(fullPath, { withFileTypes: true });
926
1369
  return items.map((item) => {
927
1370
  return `${item.isDirectory() ? "[DIR]" : "[FILE]"} ${item.name}`;
928
1371
  }).join("\n");
@@ -932,11 +1375,11 @@ function handleListFiles(dirPath) {
932
1375
  }
933
1376
  function handleReadFile(filePath, showLineNumbers = true) {
934
1377
  try {
935
- const fullPath = path4.resolve(process.cwd(), filePath);
936
- if (!fs3.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
937
- const stats = fs3.statSync(fullPath);
1378
+ const fullPath = path6.resolve(process.cwd(), filePath);
1379
+ if (!fs4.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
1380
+ const stats = fs4.statSync(fullPath);
938
1381
  if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
939
- const content = fs3.readFileSync(fullPath, "utf-8");
1382
+ const content = fs4.readFileSync(fullPath, "utf-8");
940
1383
  if (showLineNumbers) {
941
1384
  const lines = content.split("\n");
942
1385
  return lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n");
@@ -948,11 +1391,11 @@ function handleReadFile(filePath, showLineNumbers = true) {
948
1391
  }
949
1392
  function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
950
1393
  try {
951
- if (!fs3.existsSync(filePath)) {
1394
+ if (!fs4.existsSync(filePath)) {
952
1395
  tui2.log.error(`\u274C File not found for modification: ${filePath}`);
953
1396
  return false;
954
1397
  }
955
- const currentFileContent = fs3.readFileSync(filePath, "utf-8");
1398
+ const currentFileContent = fs4.readFileSync(filePath, "utf-8");
956
1399
  const lineEnding = detectLineEnding(currentFileContent);
957
1400
  const lines = currentFileContent.split(lineEnding);
958
1401
  if (startLine < 1 || startLine > lines.length) {
@@ -970,7 +1413,7 @@ function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
970
1413
  const result = [...before, ...normalizedNewLines, ...after].join(lineEnding);
971
1414
  const BOM = "\uFEFF";
972
1415
  const finalContent = result.startsWith(BOM) ? result : BOM + result;
973
- fs3.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
1416
+ fs4.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
974
1417
  tui2.log.success(`\u2705 Replaced lines ${startLine}-${endLine} in ${filePath}`);
975
1418
  return true;
976
1419
  } catch (e) {
@@ -980,9 +1423,9 @@ function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
980
1423
  }
981
1424
  async function generateFilePreview(filePath, startLine, endLine, newContent) {
982
1425
  try {
983
- const fullPath = path4.resolve(process.cwd(), filePath);
984
- if (!fs3.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
985
- const content = fs3.readFileSync(fullPath, "utf-8");
1426
+ const fullPath = path6.resolve(process.cwd(), filePath);
1427
+ if (!fs4.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
1428
+ const content = fs4.readFileSync(fullPath, "utf-8");
986
1429
  const lines = content.split(/\r\n|\r|\n/);
987
1430
  const startIdx = startLine - 1;
988
1431
  const endIdx = endLine - 1;
@@ -1029,11 +1472,11 @@ function handleSearchFile(pattern) {
1029
1472
  }
1030
1473
  }
1031
1474
  function startSmartReplace(filePath, newContent, targetContent, tui2) {
1032
- if (!fs3.existsSync(filePath)) {
1475
+ if (!fs4.existsSync(filePath)) {
1033
1476
  tui2.log.error(`\u274C File not found for modification: ${filePath}`);
1034
1477
  return false;
1035
1478
  }
1036
- const currentFileContent = fs3.readFileSync(filePath, "utf-8");
1479
+ const currentFileContent = fs4.readFileSync(filePath, "utf-8");
1037
1480
  const normalizedTarget = targetContent.replace(/\r\n/g, "\n");
1038
1481
  const normalizedContent = currentFileContent.replace(/\r\n/g, "\n");
1039
1482
  if (!normalizedContent.includes(normalizedTarget)) {
@@ -1050,7 +1493,7 @@ function startSmartReplace(filePath, newContent, targetContent, tui2) {
1050
1493
  const BOM = "\uFEFF";
1051
1494
  const updatedContent = currentFileContent.replace(targetContent, newContent);
1052
1495
  const finalContent = updatedContent.startsWith(BOM) ? updatedContent : BOM + updatedContent;
1053
- fs3.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
1496
+ fs4.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
1054
1497
  tui2.log.success(`\u2705 Smart Replace Applied: ${filePath}`);
1055
1498
  return true;
1056
1499
  }
@@ -1058,7 +1501,7 @@ async function handleRunCommand(command) {
1058
1501
  const { spawn } = await import("child_process");
1059
1502
  try {
1060
1503
  tui.log.info(`\u{1F4BB} Executing: ${colors.dim(command)}`);
1061
- return new Promise((resolve) => {
1504
+ return new Promise((resolve2) => {
1062
1505
  const child = spawn(command, {
1063
1506
  shell: true,
1064
1507
  stdio: ["ignore", "pipe", "pipe"],
@@ -1068,7 +1511,7 @@ async function handleRunCommand(command) {
1068
1511
  let stderr = "";
1069
1512
  const timer = setTimeout(() => {
1070
1513
  child.kill();
1071
- resolve(`Error: Command timed out after 5 minutes.
1514
+ resolve2(`Error: Command timed out after 5 minutes.
1072
1515
  Output so far:
1073
1516
  ${stdout}
1074
1517
  ${stderr}`);
@@ -1083,9 +1526,9 @@ ${stderr}`);
1083
1526
  child.on("close", (code) => {
1084
1527
  clearTimeout(timer);
1085
1528
  if (code === 0) {
1086
- resolve(stdout.trim() || "Command executed successfully (no output).");
1529
+ resolve2(stdout.trim() || "Command executed successfully (no output).");
1087
1530
  } else {
1088
- resolve(`Command failed with exit code ${code}.
1531
+ resolve2(`Command failed with exit code ${code}.
1089
1532
  STDERR:
1090
1533
  ${stderr}
1091
1534
  STDOUT:
@@ -1094,7 +1537,7 @@ ${stdout}`);
1094
1537
  });
1095
1538
  child.on("error", (err) => {
1096
1539
  clearTimeout(timer);
1097
- resolve(`Error executing command: ${err.message}`);
1540
+ resolve2(`Error executing command: ${err.message}`);
1098
1541
  });
1099
1542
  });
1100
1543
  } catch (e) {
@@ -1106,20 +1549,20 @@ function resolveAstGrepCommand() {
1106
1549
  const binName = isWin ? "sg.cmd" : "sg";
1107
1550
  try {
1108
1551
  const currentFile = fileURLToPath(import.meta.url);
1109
- let dir = path4.dirname(currentFile);
1552
+ let dir = path6.dirname(currentFile);
1110
1553
  for (let i = 0; i < 5; i++) {
1111
- const candidate = path4.join(dir, "node_modules", ".bin", binName);
1112
- if (fs3.existsSync(candidate)) {
1554
+ const candidate = path6.join(dir, "node_modules", ".bin", binName);
1555
+ if (fs4.existsSync(candidate)) {
1113
1556
  return `"${candidate}"`;
1114
1557
  }
1115
- const parent = path4.dirname(dir);
1558
+ const parent = path6.dirname(dir);
1116
1559
  if (parent === dir) break;
1117
1560
  dir = parent;
1118
1561
  }
1119
1562
  } catch (e) {
1120
1563
  }
1121
- const cwdBin = path4.resolve(process.cwd(), "node_modules", ".bin", binName);
1122
- if (fs3.existsSync(cwdBin)) {
1564
+ const cwdBin = path6.resolve(process.cwd(), "node_modules", ".bin", binName);
1565
+ if (fs4.existsSync(cwdBin)) {
1123
1566
  return `"${cwdBin}"`;
1124
1567
  }
1125
1568
  return "npx sg";
@@ -1127,13 +1570,13 @@ function resolveAstGrepCommand() {
1127
1570
  async function astGrepSearch(pattern, filePath, language, tui2) {
1128
1571
  const { spawn } = await import("child_process");
1129
1572
  try {
1130
- if (!fs3.existsSync(filePath)) {
1573
+ if (!fs4.existsSync(filePath)) {
1131
1574
  return `\u274C File not found: ${filePath}`;
1132
1575
  }
1133
1576
  const sgCmd = resolveAstGrepCommand();
1134
1577
  const cmd = `${sgCmd} run -p "${pattern}" -l ${language} --json ${filePath}`;
1135
1578
  tui2.log.info(`\u{1F50D} [AST-GREP] Searching: ${cmd}`);
1136
- return new Promise((resolve) => {
1579
+ return new Promise((resolve2) => {
1137
1580
  const child = spawn(cmd, {
1138
1581
  shell: true,
1139
1582
  stdio: ["ignore", "pipe", "pipe"],
@@ -1147,19 +1590,19 @@ async function astGrepSearch(pattern, filePath, language, tui2) {
1147
1590
  child.stderr.on("data", (data) => stderr += data.toString());
1148
1591
  child.on("close", (code) => {
1149
1592
  if (code === 0 && stdout) {
1150
- resolve(stdout);
1593
+ resolve2(stdout);
1151
1594
  } else if (code === 1 && !stderr) {
1152
- resolve("No structural matches found.");
1595
+ resolve2("No structural matches found.");
1153
1596
  } else {
1154
- if (!stdout && !stderr) resolve("No structural matches found.");
1597
+ if (!stdout && !stderr) resolve2("No structural matches found.");
1155
1598
  else {
1156
1599
  tui2.log.error(`\u274C ast-grep search error (code ${code}): ${stderr}`);
1157
- resolve(`Error executing ast-grep search: ${stderr || stdout}`);
1600
+ resolve2(`Error executing ast-grep search: ${stderr || stdout}`);
1158
1601
  }
1159
1602
  }
1160
1603
  });
1161
1604
  child.on("error", (err) => {
1162
- resolve(`Error executing ast-grep search: ${err.message}`);
1605
+ resolve2(`Error executing ast-grep search: ${err.message}`);
1163
1606
  });
1164
1607
  });
1165
1608
  } catch (e) {
@@ -1170,14 +1613,14 @@ async function astGrepSearch(pattern, filePath, language, tui2) {
1170
1613
  async function astGrepRewrite(pattern, fix, filePath, language, tui2) {
1171
1614
  const { spawn } = await import("child_process");
1172
1615
  try {
1173
- if (!fs3.existsSync(filePath)) {
1616
+ if (!fs4.existsSync(filePath)) {
1174
1617
  tui2.log.error(`\u274C File not found for AST modification: ${filePath}`);
1175
1618
  return false;
1176
1619
  }
1177
1620
  const sgCmd = resolveAstGrepCommand();
1178
1621
  const cmd = `${sgCmd} run -p "${pattern}" -r "${fix}" -l ${language} ${filePath} --update-all`;
1179
1622
  tui2.log.info(`\u270F\uFE0F [AST-GREP] Rewriting: pattern="${pattern}" fix="${fix.substring(0, 50)}..."`);
1180
- return new Promise((resolve) => {
1623
+ return new Promise((resolve2) => {
1181
1624
  const child = spawn(cmd, {
1182
1625
  shell: true,
1183
1626
  stdio: ["ignore", "pipe", "pipe"],
@@ -1188,15 +1631,15 @@ async function astGrepRewrite(pattern, fix, filePath, language, tui2) {
1188
1631
  child.on("close", (code) => {
1189
1632
  if (code === 0) {
1190
1633
  tui2.log.success(`\u2705 AST Rewrite applied to ${filePath}`);
1191
- resolve(true);
1634
+ resolve2(true);
1192
1635
  } else {
1193
1636
  tui2.log.error(`\u274C AST Rewrite failed (code ${code}): ${stderr}`);
1194
- resolve(false);
1637
+ resolve2(false);
1195
1638
  }
1196
1639
  });
1197
1640
  child.on("error", (err) => {
1198
1641
  tui2.log.error(`\u274C AST Rewrite spawn error: ${err.message}`);
1199
- resolve(false);
1642
+ resolve2(false);
1200
1643
  });
1201
1644
  });
1202
1645
  } catch (e) {
@@ -1204,6 +1647,160 @@ async function astGrepRewrite(pattern, fix, filePath, language, tui2) {
1204
1647
  return false;
1205
1648
  }
1206
1649
  }
1650
+ async function astListStructure(filePath) {
1651
+ try {
1652
+ const editor = CodeEditorFactory.getEditor(filePath);
1653
+ if (!editor) {
1654
+ return `[AST Error] File type not supported: ${filePath}. Use read_file instead.`;
1655
+ }
1656
+ const structure = await editor.listStructure(filePath);
1657
+ let output = `[AST Structure of ${filePath}]
1658
+
1659
+ `;
1660
+ if (structure.classes.length > 0) {
1661
+ output += `CLASSES:
1662
+ `;
1663
+ structure.classes.forEach((cls) => {
1664
+ output += ` - ${cls.name}`;
1665
+ if (cls.extendsClass) output += ` extends ${cls.extendsClass}`;
1666
+ if (cls.implementsInterfaces.length > 0) {
1667
+ output += ` implements ${cls.implementsInterfaces.join(", ")}`;
1668
+ }
1669
+ output += `
1670
+ `;
1671
+ if (cls.decorators.length > 0) {
1672
+ output += ` Decorators: ${cls.decorators.join(", ")}
1673
+ `;
1674
+ }
1675
+ if (cls.properties.length > 0) {
1676
+ output += ` Properties:
1677
+ `;
1678
+ cls.properties.forEach((prop) => {
1679
+ output += ` - ${prop.visibility} ${prop.name}: ${prop.type}
1680
+ `;
1681
+ });
1682
+ }
1683
+ if (cls.methods.length > 0) {
1684
+ output += ` Methods:
1685
+ `;
1686
+ cls.methods.forEach((method) => {
1687
+ const params = method.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
1688
+ output += ` - ${method.visibility} ${method.name}(${params}): ${method.returnType}
1689
+ `;
1690
+ });
1691
+ }
1692
+ output += `
1693
+ `;
1694
+ });
1695
+ }
1696
+ if (structure.interfaces.length > 0) {
1697
+ output += `INTERFACES:
1698
+ `;
1699
+ structure.interfaces.forEach((iface) => {
1700
+ output += ` - ${iface.name}
1701
+ `;
1702
+ iface.properties.forEach((prop) => {
1703
+ output += ` ${prop.name}: ${prop.type}
1704
+ `;
1705
+ });
1706
+ });
1707
+ output += `
1708
+ `;
1709
+ }
1710
+ if (structure.functions.length > 0) {
1711
+ output += `FUNCTIONS:
1712
+ `;
1713
+ structure.functions.forEach((fn) => {
1714
+ const params = fn.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
1715
+ output += ` - ${fn.name}(${params}): ${fn.returnType}
1716
+ `;
1717
+ });
1718
+ output += `
1719
+ `;
1720
+ }
1721
+ if (structure.imports.length > 0) {
1722
+ output += `IMPORTS:
1723
+ `;
1724
+ structure.imports.forEach((imp) => {
1725
+ output += ` - from "${imp.modulePath}": ${imp.namedImports.join(", ")}
1726
+ `;
1727
+ });
1728
+ }
1729
+ return output;
1730
+ } catch (error) {
1731
+ return `[AST Error] ${error.message}`;
1732
+ }
1733
+ }
1734
+ async function astAddMethod(filePath, className, methodCode) {
1735
+ const editor = CodeEditorFactory.getEditor(filePath);
1736
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1737
+ return await editor.addMethod(filePath, className, methodCode);
1738
+ }
1739
+ async function astAddClass(filePath, className, extendsClass, implementsInterfaces) {
1740
+ const editor = CodeEditorFactory.getEditor(filePath);
1741
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1742
+ return await editor.addClass(filePath, className, { extendsClass, implementsInterfaces });
1743
+ }
1744
+ async function astAddProperty(filePath, className, propertyCode) {
1745
+ const editor = CodeEditorFactory.getEditor(filePath);
1746
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1747
+ return await editor.addProperty(filePath, className, propertyCode);
1748
+ }
1749
+ async function astRemoveProperty(filePath, className, propertyName) {
1750
+ const editor = CodeEditorFactory.getEditor(filePath);
1751
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1752
+ return await editor.removeProperty(filePath, className, propertyName);
1753
+ }
1754
+ async function astModifyMethod(filePath, className, methodName, newBody) {
1755
+ const editor = CodeEditorFactory.getEditor(filePath);
1756
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1757
+ return await editor.modifyMethod(filePath, className, methodName, newBody);
1758
+ }
1759
+ async function astRemoveMethod(filePath, className, methodName) {
1760
+ const editor = CodeEditorFactory.getEditor(filePath);
1761
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1762
+ return await editor.removeMethod(filePath, className, methodName);
1763
+ }
1764
+ async function astAddDecorator(filePath, className, decoratorCode) {
1765
+ const editor = CodeEditorFactory.getEditor(filePath);
1766
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1767
+ return await editor.addDecorator(filePath, className, decoratorCode);
1768
+ }
1769
+ async function astAddInterface(filePath, interfaceCode) {
1770
+ const editor = CodeEditorFactory.getEditor(filePath);
1771
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1772
+ return await editor.addInterface(filePath, interfaceCode);
1773
+ }
1774
+ async function astAddTypeAlias(filePath, typeCode) {
1775
+ const editor = CodeEditorFactory.getEditor(filePath);
1776
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1777
+ return await editor.addTypeAlias(filePath, typeCode);
1778
+ }
1779
+ async function astAddFunction(filePath, functionCode) {
1780
+ const editor = CodeEditorFactory.getEditor(filePath);
1781
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1782
+ return await editor.addFunction(filePath, functionCode);
1783
+ }
1784
+ async function astRemoveFunction(filePath, functionName) {
1785
+ const editor = CodeEditorFactory.getEditor(filePath);
1786
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1787
+ return await editor.removeFunction(filePath, functionName);
1788
+ }
1789
+ async function astAddImport(filePath, importStatement) {
1790
+ const editor = CodeEditorFactory.getEditor(filePath);
1791
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1792
+ return await editor.addImport(filePath, importStatement);
1793
+ }
1794
+ async function astRemoveImport(filePath, modulePath) {
1795
+ const editor = CodeEditorFactory.getEditor(filePath);
1796
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1797
+ return await editor.removeImport(filePath, modulePath);
1798
+ }
1799
+ async function astOrganizeImports(filePath) {
1800
+ const editor = CodeEditorFactory.getEditor(filePath);
1801
+ if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
1802
+ return await editor.organizeImports(filePath);
1803
+ }
1207
1804
 
1208
1805
  // src/core/agents/specification-agent.ts
1209
1806
  var AGENT_TYPE2 = "specification_agent";
@@ -1218,23 +1815,23 @@ async function interactiveSpecificationAgent(options = {}) {
1218
1815
  tui.intro("\u{1F3D7}\uFE0F Specification Agent");
1219
1816
  const projectRoot = process.cwd();
1220
1817
  let contextContent = "";
1221
- const contextPath = path5.resolve(projectRoot, "_sharkrc", "project-context.md");
1222
- if (fs4.existsSync(contextPath)) {
1818
+ const contextPath = path7.resolve(projectRoot, "_sharkrc", "project-context.md");
1819
+ if (fs5.existsSync(contextPath)) {
1223
1820
  try {
1224
- contextContent = fs4.readFileSync(contextPath, "utf-8");
1225
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path5.relative(projectRoot, contextPath))}`);
1821
+ contextContent = fs5.readFileSync(contextPath, "utf-8");
1822
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path7.relative(projectRoot, contextPath))}`);
1226
1823
  } catch (e) {
1227
1824
  tui.log.warning(`Failed to read context file: ${e}`);
1228
1825
  }
1229
1826
  }
1230
1827
  let briefingContent = "";
1231
- if (options.briefingPath && fs4.existsSync(options.briefingPath)) {
1232
- briefingContent = fs4.readFileSync(options.briefingPath, "utf-8");
1828
+ if (options.briefingPath && fs5.existsSync(options.briefingPath)) {
1829
+ briefingContent = fs5.readFileSync(options.briefingPath, "utf-8");
1233
1830
  tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
1234
1831
  } else {
1235
- const sharkRcBriefing = path5.resolve(projectRoot, "_sharkrc", "briefing.md");
1236
- if (fs4.existsSync(sharkRcBriefing)) {
1237
- briefingContent = fs4.readFileSync(sharkRcBriefing, "utf-8");
1832
+ const sharkRcBriefing = path7.resolve(projectRoot, "_sharkrc", "briefing.md");
1833
+ if (fs5.existsSync(sharkRcBriefing)) {
1834
+ briefingContent = fs5.readFileSync(sharkRcBriefing, "utf-8");
1238
1835
  tui.log.info(`\u{1F4C4} Standard Briefing loaded: ${colors.dim("_sharkrc/briefing.md")}`);
1239
1836
  } else {
1240
1837
  tui.log.info(colors.dim("\u2139\uFE0F No briefing file found in _sharkrc/briefing.md. Starting in interactive mode."));
@@ -1349,9 +1946,9 @@ ${result}
1349
1946
  const BOM = "\uFEFF";
1350
1947
  const contentToWrite = action.content || "";
1351
1948
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
1352
- const dir = path5.dirname(action.path);
1353
- if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
1354
- fs4.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
1949
+ const dir = path7.dirname(action.path);
1950
+ if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
1951
+ fs5.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
1355
1952
  tui.log.success(`\u2705 Created: ${action.path}`);
1356
1953
  executionResults += `[Action create_file(${action.path})]: Success
1357
1954
 
@@ -1369,7 +1966,7 @@ ${result}
1369
1966
  `;
1370
1967
  }
1371
1968
  } else if (action.type === "delete_file") {
1372
- fs4.unlinkSync(action.path);
1969
+ fs5.unlinkSync(action.path);
1373
1970
  tui.log.success(`\u2705 Deleted: ${action.path}`);
1374
1971
  executionResults += `[Action delete_file(${action.path})]: Success
1375
1972
 
@@ -1498,8 +2095,8 @@ async function callSpecAgentApi(prompt, onChunk, agentId) {
1498
2095
  import { Command as Command2 } from "commander";
1499
2096
 
1500
2097
  // src/core/agents/scan-agent.ts
1501
- import fs5 from "fs";
1502
- import path6 from "path";
2098
+ import fs6 from "fs";
2099
+ import path8 from "path";
1503
2100
  var AGENT_TYPE3 = "scan_agent";
1504
2101
  function getAgentId3() {
1505
2102
  const config = ConfigManager.getInstance().getConfig();
@@ -1514,29 +2111,29 @@ async function interactiveScanAgent(options = {}) {
1514
2111
  const projectRoot = process.cwd();
1515
2112
  let outputFile;
1516
2113
  if (options.output) {
1517
- outputFile = path6.resolve(process.cwd(), options.output);
2114
+ outputFile = path8.resolve(process.cwd(), options.output);
1518
2115
  } else {
1519
- const outputDir = path6.resolve(projectRoot, "_sharkrc");
1520
- if (!fs5.existsSync(outputDir)) {
1521
- const stat = fs5.existsSync(outputDir) ? fs5.statSync(outputDir) : null;
2116
+ const outputDir = path8.resolve(projectRoot, "_sharkrc");
2117
+ if (!fs6.existsSync(outputDir)) {
2118
+ const stat = fs6.existsSync(outputDir) ? fs6.statSync(outputDir) : null;
1522
2119
  if (stat && stat.isFile()) {
1523
2120
  tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
1524
- const fallbackDir = path6.resolve(projectRoot, "_bmad/project-context");
1525
- if (!fs5.existsSync(fallbackDir)) fs5.mkdirSync(fallbackDir, { recursive: true });
1526
- outputFile = path6.join(fallbackDir, "project-context.md");
2121
+ const fallbackDir = path8.resolve(projectRoot, "_bmad/project-context");
2122
+ if (!fs6.existsSync(fallbackDir)) fs6.mkdirSync(fallbackDir, { recursive: true });
2123
+ outputFile = path8.join(fallbackDir, "project-context.md");
1527
2124
  } else {
1528
- fs5.mkdirSync(outputDir, { recursive: true });
1529
- outputFile = path6.join(outputDir, "project-context.md");
2125
+ fs6.mkdirSync(outputDir, { recursive: true });
2126
+ outputFile = path8.join(outputDir, "project-context.md");
1530
2127
  }
1531
2128
  } else {
1532
- fs5.mkdirSync(outputDir, { recursive: true });
1533
- outputFile = path6.join(outputDir, "project-context.md");
2129
+ fs6.mkdirSync(outputDir, { recursive: true });
2130
+ outputFile = path8.join(outputDir, "project-context.md");
1534
2131
  }
1535
2132
  }
1536
2133
  tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
1537
2134
  tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
1538
2135
  tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
1539
- const configFileRelative = path6.relative(projectRoot, outputFile);
2136
+ const configFileRelative = path8.relative(projectRoot, outputFile);
1540
2137
  const initialTemplate = `# Project Context
1541
2138
 
1542
2139
  ## Overview
@@ -1569,9 +2166,9 @@ async function interactiveScanAgent(options = {}) {
1569
2166
  ## Key Patterns & Conventions
1570
2167
  [TO BE ANALYZED]
1571
2168
  `;
1572
- if (!fs5.existsSync(outputFile)) {
2169
+ if (!fs6.existsSync(outputFile)) {
1573
2170
  const BOM = "\uFEFF";
1574
- fs5.writeFileSync(outputFile, BOM + initialTemplate, { encoding: "utf-8" });
2171
+ fs6.writeFileSync(outputFile, BOM + initialTemplate, { encoding: "utf-8" });
1575
2172
  tui.log.success(`${t("commands.scan.templateCreated")} ${outputFile}`);
1576
2173
  } else {
1577
2174
  tui.log.info(t("commands.scan.fileExists"));
@@ -1869,11 +2466,11 @@ ${result}
1869
2466
 
1870
2467
  `;
1871
2468
  } else if (action.type === "create_file" || action.type === "modify_file") {
1872
- const resolvedActionPath = path6.resolve(action.path || "");
1873
- const resolvedTargetPath = path6.resolve(targetPath);
2469
+ const resolvedActionPath = path8.resolve(action.path || "");
2470
+ const resolvedTargetPath = path8.resolve(targetPath);
1874
2471
  let isTarget = resolvedActionPath === resolvedTargetPath;
1875
- if (!isTarget && path6.basename(action.path || "") === "project-context.md") {
1876
- tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path6.relative(process.cwd(), targetPath)));
2472
+ if (!isTarget && path8.basename(action.path || "") === "project-context.md") {
2473
+ tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path8.relative(process.cwd(), targetPath)));
1877
2474
  isTarget = true;
1878
2475
  action.path = targetPath;
1879
2476
  }
@@ -1883,16 +2480,16 @@ ${result}
1883
2480
  if (action.type === "create_file") {
1884
2481
  const contentToWrite = action.content || "";
1885
2482
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
1886
- fs5.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
2483
+ fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
1887
2484
  tui.log.success(t("commands.scan.generated").replace("{0}", finalPath));
1888
2485
  fileCreated = true;
1889
2486
  } else {
1890
- if (fs5.existsSync(finalPath)) {
1891
- const currentContent = fs5.readFileSync(finalPath, "utf-8");
2487
+ if (fs6.existsSync(finalPath)) {
2488
+ const currentContent = fs6.readFileSync(finalPath, "utf-8");
1892
2489
  if (action.target_content && currentContent.includes(action.target_content)) {
1893
2490
  const newContent = currentContent.replace(action.target_content, action.content || "");
1894
2491
  const finalContent = newContent.startsWith(BOM) ? newContent : BOM + newContent;
1895
- fs5.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
2492
+ fs6.writeFileSync(finalPath, finalContent, { encoding: "utf-8" });
1896
2493
  tui.log.success(t("commands.scan.updated").replace("{0}", finalPath));
1897
2494
  fileCreated = true;
1898
2495
  } else {
@@ -1921,7 +2518,7 @@ ${result}
1921
2518
  }
1922
2519
  }
1923
2520
  if (fileCreated) {
1924
- const currentContent = fs5.readFileSync(targetPath, "utf-8");
2521
+ const currentContent = fs6.readFileSync(targetPath, "utf-8");
1925
2522
  const pendingSections = [];
1926
2523
  const lines = currentContent.split("\n");
1927
2524
  let currentSection = "";
@@ -2013,8 +2610,8 @@ var scanCommand = new Command2("scan").description("Analyze the project and gene
2013
2610
  import { Command as Command3 } from "commander";
2014
2611
 
2015
2612
  // src/core/agents/developer-agent.ts
2016
- import fs6 from "fs";
2017
- import path7 from "path";
2613
+ import fs7 from "fs";
2614
+ import path9 from "path";
2018
2615
  var AGENT_TYPE4 = "developer_agent";
2019
2616
  async function validateTypeScript(filePath) {
2020
2617
  try {
@@ -2028,7 +2625,7 @@ async function validateTypeScript(filePath) {
2028
2625
  }
2029
2626
  }
2030
2627
  function validateHtmlTagBalance(filePath) {
2031
- const content = fs6.readFileSync(filePath, "utf-8");
2628
+ const content = fs7.readFileSync(filePath, "utf-8");
2032
2629
  const stack = [];
2033
2630
  const tagRegex = /<\/?(\w+)(?:\s[^>]*)?\s*\/?>/g;
2034
2631
  const selfClosingTags = ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "param", "source", "track", "wbr"];
@@ -2061,11 +2658,11 @@ function getAgentId4(overrideId) {
2061
2658
  return "01KEQCGJ65YENRA4QBXVN1YFFX";
2062
2659
  }
2063
2660
  function analyzeSpecState(projectRoot) {
2064
- const specPath = path7.resolve(projectRoot, "tech-spec.md");
2065
- if (!fs6.existsSync(specPath)) {
2661
+ const specPath = path9.resolve(projectRoot, "tech-spec.md");
2662
+ if (!fs7.existsSync(specPath)) {
2066
2663
  return { status: "MISSING" };
2067
2664
  }
2068
- const content = fs6.readFileSync(specPath, "utf-8");
2665
+ const content = fs7.readFileSync(specPath, "utf-8");
2069
2666
  const match = content.match(/- \[ \] (.*)/);
2070
2667
  if (match) {
2071
2668
  return { status: "PENDING", nextTask: match[1].trim(), specContent: content };
@@ -2145,12 +2742,12 @@ async function interactiveDeveloperAgent(options = {}) {
2145
2742
  }
2146
2743
  const projectRoot = process.cwd();
2147
2744
  let contextContent = "";
2148
- const defaultContextPath = path7.resolve(projectRoot, "_sharkrc", "project-context.md");
2149
- const specificContextPath = options.context ? path7.resolve(projectRoot, options.context) : defaultContextPath;
2150
- if (fs6.existsSync(specificContextPath)) {
2745
+ const defaultContextPath = path9.resolve(projectRoot, "_sharkrc", "project-context.md");
2746
+ const specificContextPath = options.context ? path9.resolve(projectRoot, options.context) : defaultContextPath;
2747
+ if (fs7.existsSync(specificContextPath)) {
2151
2748
  try {
2152
- contextContent = fs6.readFileSync(specificContextPath, "utf-8");
2153
- tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path7.relative(projectRoot, specificContextPath))}`);
2749
+ contextContent = fs7.readFileSync(specificContextPath, "utf-8");
2750
+ tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path9.relative(projectRoot, specificContextPath))}`);
2154
2751
  } catch (e) {
2155
2752
  tui.log.warning(`Failed to read context file: ${e}`);
2156
2753
  }
@@ -2297,14 +2894,14 @@ ${previewDiff}
2297
2894
  }
2298
2895
  if (approved) {
2299
2896
  if (filePath) {
2300
- const targetPath = path7.resolve(projectRoot, filePath);
2301
- const dir = path7.dirname(targetPath);
2302
- if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
2897
+ const targetPath = path9.resolve(projectRoot, filePath);
2898
+ const dir = path9.dirname(targetPath);
2899
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2303
2900
  if (isCreate) {
2304
2901
  const BOM = "\uFEFF";
2305
2902
  const contentToWrite = action.content || "";
2306
2903
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
2307
- fs6.writeFileSync(targetPath, finalContent, { encoding: "utf-8" });
2904
+ fs7.writeFileSync(targetPath, finalContent, { encoding: "utf-8" });
2308
2905
  tui.log.success(`\u2705 Created: ${filePath}`);
2309
2906
  executionResults += `[Action create_file(${filePath})]: Success
2310
2907
 
@@ -2313,7 +2910,7 @@ ${previewDiff}
2313
2910
  const config = ConfigManager.getInstance().getConfig();
2314
2911
  const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
2315
2912
  if (validationEnabled) {
2316
- const ext = path7.extname(filePath);
2913
+ const ext = path9.extname(filePath);
2317
2914
  if ([".ts", ".tsx"].includes(ext)) {
2318
2915
  tui.log.info("\u{1F50D} Validating TypeScript...");
2319
2916
  const validation = await validateTypeScript(filePath);
@@ -2369,7 +2966,7 @@ ${validation.error}
2369
2966
  const config = ConfigManager.getInstance().getConfig();
2370
2967
  const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
2371
2968
  if (validationEnabled) {
2372
- const ext = path7.extname(filePath);
2969
+ const ext = path9.extname(filePath);
2373
2970
  if ([".ts", ".tsx"].includes(ext)) {
2374
2971
  tui.log.info("\u{1F50D} Validating TypeScript...");
2375
2972
  const validation = await validateTypeScript(filePath);
@@ -2449,7 +3046,7 @@ ${result}
2449
3046
 
2450
3047
  `;
2451
3048
  if (targetPath.endsWith("tech-spec.md")) specUpdated = true;
2452
- const ext = path7.extname(targetPath);
3049
+ const ext = path9.extname(targetPath);
2453
3050
  if ([".ts", ".tsx"].includes(ext)) {
2454
3051
  const validation = await validateTypeScript(targetPath);
2455
3052
  if (!validation.valid) {
@@ -2471,6 +3068,121 @@ ${validation.error}
2471
3068
 
2472
3069
  `;
2473
3070
  }
3071
+ } else if (action.type.startsWith("ast_")) {
3072
+ const targetPath = action.file_path || action.path || "";
3073
+ tui.log.info(`\u{1F527} [AST] Action: ${colors.bold(action.type)} on ${targetPath}`);
3074
+ if (action.type === "ast_list_structure") {
3075
+ const result = await astListStructure(targetPath);
3076
+ executionResults += `[Action ast_list_structure]:
3077
+ ${result}
3078
+
3079
+ `;
3080
+ } else {
3081
+ let approved = false;
3082
+ if (autoApprovals.files) {
3083
+ approved = true;
3084
+ tui.log.success(`\u26A1 Auto-Approved AST Action: ${action.type}`);
3085
+ } else {
3086
+ const choice = await tui.select({
3087
+ message: `Execute ${action.type} on ${targetPath}?`,
3088
+ options: [
3089
+ { value: "yes", label: "Yes" },
3090
+ { value: "always", label: "Yes (Auto-Approve ALL Files)" },
3091
+ { value: "no", label: "No" }
3092
+ ]
3093
+ });
3094
+ if (choice === "always") {
3095
+ autoApprovals.files = true;
3096
+ approved = true;
3097
+ tui.log.success("\u26A1 FILE ACTIONS Auto-Approval ENABLED for this session.");
3098
+ } else if (choice === "yes") {
3099
+ approved = true;
3100
+ }
3101
+ }
3102
+ if (approved) {
3103
+ try {
3104
+ let success = false;
3105
+ switch (action.type) {
3106
+ case "ast_add_class":
3107
+ success = await astAddClass(targetPath, action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
3108
+ break;
3109
+ case "ast_add_method":
3110
+ success = await astAddMethod(targetPath, action.class_name || "", action.method_code || "");
3111
+ break;
3112
+ case "ast_add_property":
3113
+ success = await astAddProperty(targetPath, action.class_name || "", action.property_code || "");
3114
+ break;
3115
+ case "ast_remove_property":
3116
+ success = await astRemoveProperty(targetPath, action.class_name || "", action.property_name || "");
3117
+ break;
3118
+ case "ast_modify_method":
3119
+ success = await astModifyMethod(targetPath, action.class_name || "", action.method_name || "", action.new_body || "");
3120
+ break;
3121
+ case "ast_remove_method":
3122
+ success = await astRemoveMethod(targetPath, action.class_name || "", action.method_name || "");
3123
+ break;
3124
+ case "ast_add_decorator":
3125
+ success = await astAddDecorator(targetPath, action.class_name || "", action.decorator_code || "");
3126
+ break;
3127
+ case "ast_add_interface":
3128
+ success = await astAddInterface(targetPath, action.interface_code || "");
3129
+ break;
3130
+ case "ast_add_type_alias":
3131
+ success = await astAddTypeAlias(targetPath, action.type_code || "");
3132
+ break;
3133
+ case "ast_add_function":
3134
+ success = await astAddFunction(targetPath, action.function_code || "");
3135
+ break;
3136
+ case "ast_remove_function":
3137
+ success = await astRemoveFunction(targetPath, action.function_name || "");
3138
+ break;
3139
+ case "ast_add_import":
3140
+ success = await astAddImport(targetPath, action.import_statement || "");
3141
+ break;
3142
+ case "ast_remove_import":
3143
+ success = await astRemoveImport(targetPath, action.module_path || "");
3144
+ break;
3145
+ case "ast_organize_imports":
3146
+ success = await astOrganizeImports(targetPath);
3147
+ break;
3148
+ }
3149
+ if (success) {
3150
+ executionResults += `[Action ${action.type}]: Success
3151
+
3152
+ `;
3153
+ const config = ConfigManager.getInstance().getConfig();
3154
+ const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
3155
+ if (validationEnabled) {
3156
+ const ext = path9.extname(targetPath);
3157
+ if ([".ts", ".tsx"].includes(ext)) {
3158
+ const validation = await validateTypeScript(targetPath);
3159
+ if (!validation.valid) {
3160
+ executionResults += `
3161
+ [TYPESCRIPT VALIDATION FAILED]:
3162
+ ${validation.error}
3163
+
3164
+ `;
3165
+ } else {
3166
+ tui.log.success("\u2705 TypeScript OK");
3167
+ }
3168
+ }
3169
+ }
3170
+ } else {
3171
+ executionResults += `[Action ${action.type}]: Failed (internal editor returned false).
3172
+
3173
+ `;
3174
+ }
3175
+ } catch (e) {
3176
+ executionResults += `[Action ${action.type}]: Exception: ${e.message}
3177
+
3178
+ `;
3179
+ }
3180
+ } else {
3181
+ executionResults += `[Action ${action.type}]: User Denied.
3182
+
3183
+ `;
3184
+ }
3185
+ }
2474
3186
  }
2475
3187
  }
2476
3188
  const previousState = specState;
@@ -2586,8 +3298,8 @@ var devCommand = new Command3("dev").description("Starts the Shark Developer Age
2586
3298
  import { Command as Command4 } from "commander";
2587
3299
 
2588
3300
  // src/core/agents/qa-agent.ts
2589
- import fs7 from "fs";
2590
- import path8 from "path";
3301
+ import fs8 from "fs";
3302
+ import path10 from "path";
2591
3303
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2592
3304
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2593
3305
  var AGENT_TYPE5 = "qa_agent";
@@ -2656,9 +3368,9 @@ async function runQAAgent(options) {
2656
3368
  }
2657
3369
  let projectContext = "";
2658
3370
  try {
2659
- const contextPath = path8.join(process.cwd(), "_sharkrc", "project-context.md");
2660
- if (fs7.existsSync(contextPath)) {
2661
- projectContext = fs7.readFileSync(contextPath, "utf-8");
3371
+ const contextPath = path10.join(process.cwd(), "_sharkrc", "project-context.md");
3372
+ if (fs8.existsSync(contextPath)) {
3373
+ projectContext = fs8.readFileSync(contextPath, "utf-8");
2662
3374
  tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
2663
3375
  }
2664
3376
  } catch (e) {
@@ -2767,11 +3479,11 @@ ${projectContext}
2767
3479
  break;
2768
3480
  case "create_file":
2769
3481
  if (action.path && action.content) {
2770
- const fullPath = path8.resolve(process.cwd(), action.path);
3482
+ const fullPath = path10.resolve(process.cwd(), action.path);
2771
3483
  const BOM = "\uFEFF";
2772
3484
  const contentToWrite = action.content;
2773
3485
  const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
2774
- fs7.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
3486
+ fs8.writeFileSync(fullPath, finalContent, { encoding: "utf-8" });
2775
3487
  tui.log.success(`File created: ${action.path}`);
2776
3488
  result = "File created successfully.";
2777
3489
  }