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 +824 -112
- package/dist/bin/shark.js.map +1 -1
- package/package.json +3 -2
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([
|
|
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
|
|
776
|
-
import
|
|
833
|
+
import fs5 from "fs";
|
|
834
|
+
import path7 from "path";
|
|
777
835
|
|
|
778
836
|
// src/core/agents/agent-tools.ts
|
|
779
|
-
import
|
|
780
|
-
import
|
|
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
|
|
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 =
|
|
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: ${
|
|
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:
|
|
837
|
-
use_conversation:
|
|
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 =
|
|
924
|
-
if (!
|
|
925
|
-
const items =
|
|
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 =
|
|
936
|
-
if (!
|
|
937
|
-
const stats =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
984
|
-
if (!
|
|
985
|
-
const content =
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
1529
|
+
resolve2(stdout.trim() || "Command executed successfully (no output).");
|
|
1087
1530
|
} else {
|
|
1088
|
-
|
|
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
|
-
|
|
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 =
|
|
1552
|
+
let dir = path6.dirname(currentFile);
|
|
1110
1553
|
for (let i = 0; i < 5; i++) {
|
|
1111
|
-
const candidate =
|
|
1112
|
-
if (
|
|
1554
|
+
const candidate = path6.join(dir, "node_modules", ".bin", binName);
|
|
1555
|
+
if (fs4.existsSync(candidate)) {
|
|
1113
1556
|
return `"${candidate}"`;
|
|
1114
1557
|
}
|
|
1115
|
-
const parent =
|
|
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 =
|
|
1122
|
-
if (
|
|
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 (!
|
|
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((
|
|
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
|
-
|
|
1593
|
+
resolve2(stdout);
|
|
1151
1594
|
} else if (code === 1 && !stderr) {
|
|
1152
|
-
|
|
1595
|
+
resolve2("No structural matches found.");
|
|
1153
1596
|
} else {
|
|
1154
|
-
if (!stdout && !stderr)
|
|
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
|
-
|
|
1600
|
+
resolve2(`Error executing ast-grep search: ${stderr || stdout}`);
|
|
1158
1601
|
}
|
|
1159
1602
|
}
|
|
1160
1603
|
});
|
|
1161
1604
|
child.on("error", (err) => {
|
|
1162
|
-
|
|
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 (!
|
|
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((
|
|
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
|
-
|
|
1634
|
+
resolve2(true);
|
|
1192
1635
|
} else {
|
|
1193
1636
|
tui2.log.error(`\u274C AST Rewrite failed (code ${code}): ${stderr}`);
|
|
1194
|
-
|
|
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
|
-
|
|
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 =
|
|
1222
|
-
if (
|
|
1818
|
+
const contextPath = path7.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
1819
|
+
if (fs5.existsSync(contextPath)) {
|
|
1223
1820
|
try {
|
|
1224
|
-
contextContent =
|
|
1225
|
-
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(
|
|
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 &&
|
|
1232
|
-
briefingContent =
|
|
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 =
|
|
1236
|
-
if (
|
|
1237
|
-
briefingContent =
|
|
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 =
|
|
1353
|
-
if (!
|
|
1354
|
-
|
|
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
|
-
|
|
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
|
|
1502
|
-
import
|
|
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 =
|
|
2114
|
+
outputFile = path8.resolve(process.cwd(), options.output);
|
|
1518
2115
|
} else {
|
|
1519
|
-
const outputDir =
|
|
1520
|
-
if (!
|
|
1521
|
-
const stat =
|
|
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 =
|
|
1525
|
-
if (!
|
|
1526
|
-
outputFile =
|
|
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
|
-
|
|
1529
|
-
outputFile =
|
|
2125
|
+
fs6.mkdirSync(outputDir, { recursive: true });
|
|
2126
|
+
outputFile = path8.join(outputDir, "project-context.md");
|
|
1530
2127
|
}
|
|
1531
2128
|
} else {
|
|
1532
|
-
|
|
1533
|
-
outputFile =
|
|
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 =
|
|
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 (!
|
|
2169
|
+
if (!fs6.existsSync(outputFile)) {
|
|
1573
2170
|
const BOM = "\uFEFF";
|
|
1574
|
-
|
|
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 =
|
|
1873
|
-
const resolvedTargetPath =
|
|
2469
|
+
const resolvedActionPath = path8.resolve(action.path || "");
|
|
2470
|
+
const resolvedTargetPath = path8.resolve(targetPath);
|
|
1874
2471
|
let isTarget = resolvedActionPath === resolvedTargetPath;
|
|
1875
|
-
if (!isTarget &&
|
|
1876
|
-
tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}",
|
|
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
|
-
|
|
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 (
|
|
1891
|
-
const currentContent =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
2017
|
-
import
|
|
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 =
|
|
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 =
|
|
2065
|
-
if (!
|
|
2661
|
+
const specPath = path9.resolve(projectRoot, "tech-spec.md");
|
|
2662
|
+
if (!fs7.existsSync(specPath)) {
|
|
2066
2663
|
return { status: "MISSING" };
|
|
2067
2664
|
}
|
|
2068
|
-
const content =
|
|
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 =
|
|
2149
|
-
const specificContextPath = options.context ?
|
|
2150
|
-
if (
|
|
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 =
|
|
2153
|
-
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(
|
|
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 =
|
|
2301
|
-
const dir =
|
|
2302
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2590
|
-
import
|
|
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 =
|
|
2660
|
-
if (
|
|
2661
|
-
projectContext =
|
|
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 =
|
|
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
|
-
|
|
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
|
}
|