shark-ai 0.3.14 → 0.3.16
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
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
t,
|
|
10
10
|
tokenStorage,
|
|
11
11
|
tui
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-WVBOO6LE.js";
|
|
13
13
|
|
|
14
14
|
// src/core/error/crash-handler.ts
|
|
15
15
|
import fs from "fs";
|
|
@@ -773,15 +773,139 @@ async function interactiveBusinessAnalyst() {
|
|
|
773
773
|
|
|
774
774
|
// src/core/agents/specification-agent.ts
|
|
775
775
|
import fs4 from "fs";
|
|
776
|
-
import
|
|
776
|
+
import path5 from "path";
|
|
777
777
|
|
|
778
778
|
// src/core/agents/agent-tools.ts
|
|
779
779
|
import fs3 from "fs";
|
|
780
|
-
import
|
|
780
|
+
import path4 from "path";
|
|
781
781
|
import fg from "fast-glob";
|
|
782
782
|
import { exec } from "child_process";
|
|
783
783
|
import { promisify } from "util";
|
|
784
784
|
import { fileURLToPath } from "url";
|
|
785
|
+
|
|
786
|
+
// src/core/services/code-review.service.ts
|
|
787
|
+
import path3 from "path";
|
|
788
|
+
var CodeReviewService = class {
|
|
789
|
+
/**
|
|
790
|
+
* Reviews the code modification and returns a status/feedback string.
|
|
791
|
+
* This string is intended to be appended to the user Preview so the Agent can see it.
|
|
792
|
+
*/
|
|
793
|
+
static async reviewCode(filePath, newContent) {
|
|
794
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
795
|
+
const validation = config.validation || { llmReviewExtensions: [".ts", ".tsx"], syntaxCheckExtensions: ["*"] };
|
|
796
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
797
|
+
const needsLlmReview = validation.llmReviewExtensions.includes(ext) || validation.llmReviewExtensions.includes("*");
|
|
798
|
+
if (needsLlmReview) {
|
|
799
|
+
return await this.performLlmReview(filePath, newContent);
|
|
800
|
+
}
|
|
801
|
+
const needsSyntaxCheck = validation.syntaxCheckExtensions.includes(ext) || validation.syntaxCheckExtensions.includes("*");
|
|
802
|
+
if (needsSyntaxCheck) {
|
|
803
|
+
return this.performSyntaxCheck(filePath, newContent);
|
|
804
|
+
}
|
|
805
|
+
return "\u2139\uFE0F No specific validation configured for this file type.";
|
|
806
|
+
}
|
|
807
|
+
static getCodeReviewAgentId() {
|
|
808
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
809
|
+
if (config.agents?.codeReview) return config.agents.codeReview;
|
|
810
|
+
if (process.env.STACKSPOT_CODE_REVIEW_AGENT_ID) return process.env.STACKSPOT_CODE_REVIEW_AGENT_ID;
|
|
811
|
+
return "";
|
|
812
|
+
}
|
|
813
|
+
static async performLlmReview(filePath, content) {
|
|
814
|
+
const agentId = this.getCodeReviewAgentId();
|
|
815
|
+
if (!agentId) {
|
|
816
|
+
return this.performSyntaxCheck(filePath, content);
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const realm = await getActiveRealm();
|
|
820
|
+
const token = await ensureValidToken(realm);
|
|
821
|
+
const prompt = `You are a Code Review Specialist. Analyze the following code modification for file: ${path3.basename(filePath)}
|
|
822
|
+
|
|
823
|
+
NEW CODE TO BE ADDED:
|
|
824
|
+
\`\`\`
|
|
825
|
+
${content}
|
|
826
|
+
\`\`\`
|
|
827
|
+
|
|
828
|
+
Provide a concise review focusing on:
|
|
829
|
+
1. Syntax errors or obvious bugs
|
|
830
|
+
2. Potential runtime issues
|
|
831
|
+
3. Code quality concerns
|
|
832
|
+
|
|
833
|
+
Respond in JSON format with status, issues array, and summary.`;
|
|
834
|
+
const payload = {
|
|
835
|
+
user_prompt: prompt,
|
|
836
|
+
streaming: false,
|
|
837
|
+
use_conversation: false,
|
|
838
|
+
stackspot_knowledge: false
|
|
839
|
+
};
|
|
840
|
+
const url = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${agentId}/chat`;
|
|
841
|
+
let reviewText = "";
|
|
842
|
+
await sseClient.streamAgentResponse(url, payload, { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, {
|
|
843
|
+
onChunk: (c) => {
|
|
844
|
+
reviewText += c;
|
|
845
|
+
},
|
|
846
|
+
onComplete: () => {
|
|
847
|
+
},
|
|
848
|
+
onError: (e) => {
|
|
849
|
+
throw e;
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
try {
|
|
853
|
+
const reviewData = JSON.parse(reviewText.trim());
|
|
854
|
+
return this.formatReviewResponse(reviewData);
|
|
855
|
+
} catch (parseErr) {
|
|
856
|
+
return `\u{1F916} **[AI Code Review Agent]**
|
|
857
|
+
\u26A0\uFE0F Response format error. Raw output:
|
|
858
|
+
${reviewText.trim()}`;
|
|
859
|
+
}
|
|
860
|
+
} catch (err) {
|
|
861
|
+
FileLogger.log("CODE_REVIEW", "LLM Review Failed", { error: err.message });
|
|
862
|
+
return `\u26A0\uFE0F AI Review unavailable (${err.message}). Using syntax check:
|
|
863
|
+
` + await this.performSyntaxCheck(filePath, content);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
static formatReviewResponse(data) {
|
|
867
|
+
let report = `\u{1F916} **[AI Code Review Agent]**
|
|
868
|
+
`;
|
|
869
|
+
const statusIcon = data.status === "ok" ? "\u2705" : data.status === "warning" ? "\u26A0\uFE0F" : "\u274C";
|
|
870
|
+
report += `${statusIcon} **Status:** ${data.status.toUpperCase()}
|
|
871
|
+
`;
|
|
872
|
+
if (data.issues && data.issues.length > 0) {
|
|
873
|
+
report += `
|
|
874
|
+
**Issues Found:**
|
|
875
|
+
`;
|
|
876
|
+
for (const issue of data.issues) {
|
|
877
|
+
const severityIcon = issue.severity === "error" ? "\u274C" : issue.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
878
|
+
const lineInfo = issue.line ? `Linha ${issue.line}: ` : "";
|
|
879
|
+
report += `${severityIcon} ${lineInfo}${issue.message}
|
|
880
|
+
`;
|
|
881
|
+
if (issue.suggestion) {
|
|
882
|
+
report += ` \u{1F4A1} ${issue.suggestion}
|
|
883
|
+
`;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
report += `
|
|
888
|
+
**Resumo:** ${data.summary}`;
|
|
889
|
+
return report;
|
|
890
|
+
}
|
|
891
|
+
static performSyntaxCheck(filePath, content) {
|
|
892
|
+
let report = `\u{1F50D} **[Syntax Validator]**
|
|
893
|
+
`;
|
|
894
|
+
const openBraces = (content.match(/\{/g) || []).length;
|
|
895
|
+
const closeBraces = (content.match(/\}/g) || []).length;
|
|
896
|
+
if (openBraces !== closeBraces) {
|
|
897
|
+
report += `\u274C **CRITICAL:** Brace mismatch detected! Found ${openBraces} '{' and ${closeBraces} '}'.
|
|
898
|
+
`;
|
|
899
|
+
report += ` **Review your code carefully. Do not confirm if the block is incomplete.**`;
|
|
900
|
+
} else {
|
|
901
|
+
report += `\u2705 File structure looks balanced (Braces match).
|
|
902
|
+
`;
|
|
903
|
+
}
|
|
904
|
+
return Promise.resolve(report);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/core/agents/agent-tools.ts
|
|
785
909
|
var execAsync = promisify(exec);
|
|
786
910
|
function detectLineEnding(content) {
|
|
787
911
|
const crlf = content.split("\r\n").length - 1;
|
|
@@ -790,7 +914,7 @@ function detectLineEnding(content) {
|
|
|
790
914
|
}
|
|
791
915
|
function handleListFiles(dirPath) {
|
|
792
916
|
try {
|
|
793
|
-
const fullPath =
|
|
917
|
+
const fullPath = path4.resolve(process.cwd(), dirPath);
|
|
794
918
|
if (!fs3.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
|
|
795
919
|
const items = fs3.readdirSync(fullPath, { withFileTypes: true });
|
|
796
920
|
return items.map((item) => {
|
|
@@ -802,7 +926,7 @@ function handleListFiles(dirPath) {
|
|
|
802
926
|
}
|
|
803
927
|
function handleReadFile(filePath, showLineNumbers = true) {
|
|
804
928
|
try {
|
|
805
|
-
const fullPath =
|
|
929
|
+
const fullPath = path4.resolve(process.cwd(), filePath);
|
|
806
930
|
if (!fs3.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
|
|
807
931
|
const stats = fs3.statSync(fullPath);
|
|
808
932
|
if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
|
|
@@ -848,9 +972,9 @@ function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
|
|
|
848
972
|
return false;
|
|
849
973
|
}
|
|
850
974
|
}
|
|
851
|
-
function generateFilePreview(filePath, startLine, endLine, newContent) {
|
|
975
|
+
async function generateFilePreview(filePath, startLine, endLine, newContent) {
|
|
852
976
|
try {
|
|
853
|
-
const fullPath =
|
|
977
|
+
const fullPath = path4.resolve(process.cwd(), filePath);
|
|
854
978
|
if (!fs3.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
|
|
855
979
|
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
856
980
|
const lines = content.split(/\r\n|\r|\n/);
|
|
@@ -862,6 +986,12 @@ function generateFilePreview(filePath, startLine, endLine, newContent) {
|
|
|
862
986
|
const contentReplacing = lines.slice(startIdx, endIdx + 1).map((l, i) => `${startLine + i} | - ${l}`).join("\n");
|
|
863
987
|
const contextAfter = lines.slice(endIdx + 1, Math.min(lines.length, endIdx + 4)).map((l, i) => `${endLine + 1 + i} | ${l}`).join("\n");
|
|
864
988
|
const newLines = newContent.split("\n").map((l) => `+ ${l}`).join("\n");
|
|
989
|
+
let reviewFeedback = "";
|
|
990
|
+
try {
|
|
991
|
+
reviewFeedback = await CodeReviewService.reviewCode(filePath, newContent);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
reviewFeedback = `\u26A0\uFE0F Code Review Service Unavailable: ${err.message}`;
|
|
994
|
+
}
|
|
865
995
|
return `PREVIEW OF CHANGES to ${filePath}:
|
|
866
996
|
--------------------------------------------------
|
|
867
997
|
CONTEXT BEFORE:
|
|
@@ -874,6 +1004,8 @@ ${newLines}
|
|
|
874
1004
|
CONTEXT AFTER:
|
|
875
1005
|
${contextAfter}
|
|
876
1006
|
--------------------------------------------------
|
|
1007
|
+
${reviewFeedback}
|
|
1008
|
+
|
|
877
1009
|
IMPORTANT: Please verify that the lines being replaced (marked with -) are exactly what you intend to remove.
|
|
878
1010
|
If the context looks wrong, DO NOT CONFIRM. Re-read the file to check line numbers.
|
|
879
1011
|
`;
|
|
@@ -968,19 +1100,19 @@ function resolveAstGrepCommand() {
|
|
|
968
1100
|
const binName = isWin ? "sg.cmd" : "sg";
|
|
969
1101
|
try {
|
|
970
1102
|
const currentFile = fileURLToPath(import.meta.url);
|
|
971
|
-
let dir =
|
|
1103
|
+
let dir = path4.dirname(currentFile);
|
|
972
1104
|
for (let i = 0; i < 5; i++) {
|
|
973
|
-
const candidate =
|
|
1105
|
+
const candidate = path4.join(dir, "node_modules", ".bin", binName);
|
|
974
1106
|
if (fs3.existsSync(candidate)) {
|
|
975
1107
|
return `"${candidate}"`;
|
|
976
1108
|
}
|
|
977
|
-
const parent =
|
|
1109
|
+
const parent = path4.dirname(dir);
|
|
978
1110
|
if (parent === dir) break;
|
|
979
1111
|
dir = parent;
|
|
980
1112
|
}
|
|
981
1113
|
} catch (e) {
|
|
982
1114
|
}
|
|
983
|
-
const cwdBin =
|
|
1115
|
+
const cwdBin = path4.resolve(process.cwd(), "node_modules", ".bin", binName);
|
|
984
1116
|
if (fs3.existsSync(cwdBin)) {
|
|
985
1117
|
return `"${cwdBin}"`;
|
|
986
1118
|
}
|
|
@@ -1080,11 +1212,11 @@ async function interactiveSpecificationAgent(options = {}) {
|
|
|
1080
1212
|
tui.intro("\u{1F3D7}\uFE0F Specification Agent");
|
|
1081
1213
|
const projectRoot = process.cwd();
|
|
1082
1214
|
let contextContent = "";
|
|
1083
|
-
const contextPath =
|
|
1215
|
+
const contextPath = path5.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
1084
1216
|
if (fs4.existsSync(contextPath)) {
|
|
1085
1217
|
try {
|
|
1086
1218
|
contextContent = fs4.readFileSync(contextPath, "utf-8");
|
|
1087
|
-
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(
|
|
1219
|
+
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path5.relative(projectRoot, contextPath))}`);
|
|
1088
1220
|
} catch (e) {
|
|
1089
1221
|
tui.log.warning(`Failed to read context file: ${e}`);
|
|
1090
1222
|
}
|
|
@@ -1094,7 +1226,7 @@ async function interactiveSpecificationAgent(options = {}) {
|
|
|
1094
1226
|
briefingContent = fs4.readFileSync(options.briefingPath, "utf-8");
|
|
1095
1227
|
tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
|
|
1096
1228
|
} else {
|
|
1097
|
-
const sharkRcBriefing =
|
|
1229
|
+
const sharkRcBriefing = path5.resolve(projectRoot, "_sharkrc", "briefing.md");
|
|
1098
1230
|
if (fs4.existsSync(sharkRcBriefing)) {
|
|
1099
1231
|
briefingContent = fs4.readFileSync(sharkRcBriefing, "utf-8");
|
|
1100
1232
|
tui.log.info(`\u{1F4C4} Standard Briefing loaded: ${colors.dim("_sharkrc/briefing.md")}`);
|
|
@@ -1211,7 +1343,7 @@ ${result}
|
|
|
1211
1343
|
const BOM = "\uFEFF";
|
|
1212
1344
|
const contentToWrite = action.content || "";
|
|
1213
1345
|
const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
|
|
1214
|
-
const dir =
|
|
1346
|
+
const dir = path5.dirname(action.path);
|
|
1215
1347
|
if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
|
|
1216
1348
|
fs4.writeFileSync(action.path, finalContent, { encoding: "utf-8" });
|
|
1217
1349
|
tui.log.success(`\u2705 Created: ${action.path}`);
|
|
@@ -1361,7 +1493,7 @@ import { Command as Command2 } from "commander";
|
|
|
1361
1493
|
|
|
1362
1494
|
// src/core/agents/scan-agent.ts
|
|
1363
1495
|
import fs5 from "fs";
|
|
1364
|
-
import
|
|
1496
|
+
import path6 from "path";
|
|
1365
1497
|
var AGENT_TYPE3 = "scan_agent";
|
|
1366
1498
|
function getAgentId3() {
|
|
1367
1499
|
const config = ConfigManager.getInstance().getConfig();
|
|
@@ -1376,29 +1508,29 @@ async function interactiveScanAgent(options = {}) {
|
|
|
1376
1508
|
const projectRoot = process.cwd();
|
|
1377
1509
|
let outputFile;
|
|
1378
1510
|
if (options.output) {
|
|
1379
|
-
outputFile =
|
|
1511
|
+
outputFile = path6.resolve(process.cwd(), options.output);
|
|
1380
1512
|
} else {
|
|
1381
|
-
const outputDir =
|
|
1513
|
+
const outputDir = path6.resolve(projectRoot, "_sharkrc");
|
|
1382
1514
|
if (!fs5.existsSync(outputDir)) {
|
|
1383
1515
|
const stat = fs5.existsSync(outputDir) ? fs5.statSync(outputDir) : null;
|
|
1384
1516
|
if (stat && stat.isFile()) {
|
|
1385
1517
|
tui.log.warning(`Warning: '_sharkrc' exists as a file. Using '_bmad/project-context' instead to avoid overwrite.`);
|
|
1386
|
-
const fallbackDir =
|
|
1518
|
+
const fallbackDir = path6.resolve(projectRoot, "_bmad/project-context");
|
|
1387
1519
|
if (!fs5.existsSync(fallbackDir)) fs5.mkdirSync(fallbackDir, { recursive: true });
|
|
1388
|
-
outputFile =
|
|
1520
|
+
outputFile = path6.join(fallbackDir, "project-context.md");
|
|
1389
1521
|
} else {
|
|
1390
1522
|
fs5.mkdirSync(outputDir, { recursive: true });
|
|
1391
|
-
outputFile =
|
|
1523
|
+
outputFile = path6.join(outputDir, "project-context.md");
|
|
1392
1524
|
}
|
|
1393
1525
|
} else {
|
|
1394
1526
|
fs5.mkdirSync(outputDir, { recursive: true });
|
|
1395
|
-
outputFile =
|
|
1527
|
+
outputFile = path6.join(outputDir, "project-context.md");
|
|
1396
1528
|
}
|
|
1397
1529
|
}
|
|
1398
1530
|
tui.log.info(`${t("commands.scan.scanningProject")} ${colors.bold(projectRoot)}`);
|
|
1399
1531
|
tui.log.info(`${t("commands.scan.outputTarget")} ${colors.bold(outputFile)}`);
|
|
1400
1532
|
tui.log.info(`${t("commands.scan.language")} ${colors.bold(language)}`);
|
|
1401
|
-
const configFileRelative =
|
|
1533
|
+
const configFileRelative = path6.relative(projectRoot, outputFile);
|
|
1402
1534
|
const initialTemplate = `# Project Context
|
|
1403
1535
|
|
|
1404
1536
|
## Overview
|
|
@@ -1731,11 +1863,11 @@ ${result}
|
|
|
1731
1863
|
|
|
1732
1864
|
`;
|
|
1733
1865
|
} else if (action.type === "create_file" || action.type === "modify_file") {
|
|
1734
|
-
const resolvedActionPath =
|
|
1735
|
-
const resolvedTargetPath =
|
|
1866
|
+
const resolvedActionPath = path6.resolve(action.path || "");
|
|
1867
|
+
const resolvedTargetPath = path6.resolve(targetPath);
|
|
1736
1868
|
let isTarget = resolvedActionPath === resolvedTargetPath;
|
|
1737
|
-
if (!isTarget &&
|
|
1738
|
-
tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}",
|
|
1869
|
+
if (!isTarget && path6.basename(action.path || "") === "project-context.md") {
|
|
1870
|
+
tui.log.warning(t("commands.scan.targetRedirect").replace("{0}", action.path || "").replace("{1}", path6.relative(process.cwd(), targetPath)));
|
|
1739
1871
|
isTarget = true;
|
|
1740
1872
|
action.path = targetPath;
|
|
1741
1873
|
}
|
|
@@ -1876,7 +2008,7 @@ import { Command as Command3 } from "commander";
|
|
|
1876
2008
|
|
|
1877
2009
|
// src/core/agents/developer-agent.ts
|
|
1878
2010
|
import fs6 from "fs";
|
|
1879
|
-
import
|
|
2011
|
+
import path7 from "path";
|
|
1880
2012
|
var AGENT_TYPE4 = "developer_agent";
|
|
1881
2013
|
async function validateTypeScript(filePath) {
|
|
1882
2014
|
try {
|
|
@@ -1923,7 +2055,7 @@ function getAgentId4(overrideId) {
|
|
|
1923
2055
|
return "01KEQCGJ65YENRA4QBXVN1YFFX";
|
|
1924
2056
|
}
|
|
1925
2057
|
function analyzeSpecState(projectRoot) {
|
|
1926
|
-
const specPath =
|
|
2058
|
+
const specPath = path7.resolve(projectRoot, "tech-spec.md");
|
|
1927
2059
|
if (!fs6.existsSync(specPath)) {
|
|
1928
2060
|
return { status: "MISSING" };
|
|
1929
2061
|
}
|
|
@@ -2007,12 +2139,12 @@ async function interactiveDeveloperAgent(options = {}) {
|
|
|
2007
2139
|
}
|
|
2008
2140
|
const projectRoot = process.cwd();
|
|
2009
2141
|
let contextContent = "";
|
|
2010
|
-
const defaultContextPath =
|
|
2011
|
-
const specificContextPath = options.context ?
|
|
2142
|
+
const defaultContextPath = path7.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
2143
|
+
const specificContextPath = options.context ? path7.resolve(projectRoot, options.context) : defaultContextPath;
|
|
2012
2144
|
if (fs6.existsSync(specificContextPath)) {
|
|
2013
2145
|
try {
|
|
2014
2146
|
contextContent = fs6.readFileSync(specificContextPath, "utf-8");
|
|
2015
|
-
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(
|
|
2147
|
+
tui.log.info(`\u{1F4D8} Context loaded from: ${colors.dim(path7.relative(projectRoot, specificContextPath))}`);
|
|
2016
2148
|
} catch (e) {
|
|
2017
2149
|
tui.log.warning(`Failed to read context file: ${e}`);
|
|
2018
2150
|
}
|
|
@@ -2121,13 +2253,13 @@ ${result}
|
|
|
2121
2253
|
if (!isCreate && action.line_range && !action.confirmed) {
|
|
2122
2254
|
tui.log.info("\u{1F6E1}\uFE0F Generation Preview for Agent Verification...");
|
|
2123
2255
|
const [start, end] = action.line_range;
|
|
2124
|
-
const previewDiff = generateFilePreview(filePath, start, end, action.content || "");
|
|
2256
|
+
const previewDiff = await generateFilePreview(filePath, start, end, action.content || "");
|
|
2125
2257
|
executionResults += `[Action modify_file]: PENDING CONFIRMATION.
|
|
2126
2258
|
|
|
2127
2259
|
${previewDiff}
|
|
2128
2260
|
|
|
2129
2261
|
`;
|
|
2130
|
-
executionResults += `USER/SYSTEM INSTRUCTION: Please review the above preview carefully.
|
|
2262
|
+
executionResults += `USER/SYSTEM INSTRUCTION: Please review the above preview carefully.
|
|
2131
2263
|
1. If the context and changes look correct, call 'modify_file' again with the SAME parameters AND "confirmed": true.
|
|
2132
2264
|
2. If the context is wrong (e.g. wrong line numbers), call 'read_file' to check line numbers again, then correct your request.
|
|
2133
2265
|
|
|
@@ -2159,8 +2291,8 @@ ${previewDiff}
|
|
|
2159
2291
|
}
|
|
2160
2292
|
if (approved) {
|
|
2161
2293
|
if (filePath) {
|
|
2162
|
-
const targetPath =
|
|
2163
|
-
const dir =
|
|
2294
|
+
const targetPath = path7.resolve(projectRoot, filePath);
|
|
2295
|
+
const dir = path7.dirname(targetPath);
|
|
2164
2296
|
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
2165
2297
|
if (isCreate) {
|
|
2166
2298
|
const BOM = "\uFEFF";
|
|
@@ -2172,37 +2304,41 @@ ${previewDiff}
|
|
|
2172
2304
|
|
|
2173
2305
|
`;
|
|
2174
2306
|
if (filePath.endsWith("tech-spec.md")) specUpdated = true;
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
const
|
|
2179
|
-
if (
|
|
2180
|
-
tui.log.
|
|
2181
|
-
|
|
2307
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
2308
|
+
const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
|
|
2309
|
+
if (validationEnabled) {
|
|
2310
|
+
const ext = path7.extname(filePath);
|
|
2311
|
+
if ([".ts", ".tsx"].includes(ext)) {
|
|
2312
|
+
tui.log.info("\u{1F50D} Validating TypeScript...");
|
|
2313
|
+
const validation = await validateTypeScript(filePath);
|
|
2314
|
+
if (!validation.valid) {
|
|
2315
|
+
tui.log.error("\u274C TypeScript validation failed");
|
|
2316
|
+
executionResults += `
|
|
2182
2317
|
[TYPESCRIPT VALIDATION FAILED]:
|
|
2183
2318
|
${validation.error}
|
|
2184
2319
|
|
|
2185
2320
|
`;
|
|
2186
|
-
|
|
2321
|
+
executionResults += `CRITICAL: The file has been saved but contains errors. Read the file again to verify line numbers before attempting to fix. Do not guess line numbers.
|
|
2187
2322
|
`;
|
|
2188
|
-
|
|
2189
|
-
|
|
2323
|
+
} else {
|
|
2324
|
+
tui.log.success("\u2705 TypeScript OK");
|
|
2325
|
+
}
|
|
2190
2326
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
executionResults += `
|
|
2327
|
+
if (ext === ".html") {
|
|
2328
|
+
tui.log.info("\u{1F50D} Validating HTML...");
|
|
2329
|
+
const validation = validateHtmlTagBalance(filePath);
|
|
2330
|
+
if (!validation.valid) {
|
|
2331
|
+
tui.log.error("\u274C HTML validation failed");
|
|
2332
|
+
executionResults += `
|
|
2198
2333
|
[HTML VALIDATION FAILED]:
|
|
2199
2334
|
${validation.error}
|
|
2200
2335
|
|
|
2201
2336
|
`;
|
|
2202
|
-
|
|
2337
|
+
executionResults += `CRITICAL: Fix these errors before proceeding to next task.
|
|
2203
2338
|
`;
|
|
2204
|
-
|
|
2205
|
-
|
|
2339
|
+
} else {
|
|
2340
|
+
tui.log.success("\u2705 HTML OK");
|
|
2341
|
+
}
|
|
2206
2342
|
}
|
|
2207
2343
|
}
|
|
2208
2344
|
} else {
|
|
@@ -2224,37 +2360,41 @@ ${validation.error}
|
|
|
2224
2360
|
|
|
2225
2361
|
`;
|
|
2226
2362
|
if (filePath.endsWith("tech-spec.md")) specUpdated = true;
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
const
|
|
2231
|
-
if (
|
|
2232
|
-
tui.log.
|
|
2233
|
-
|
|
2363
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
2364
|
+
const validationEnabled = config.validation?.enablePostSaveValidation ?? true;
|
|
2365
|
+
if (validationEnabled) {
|
|
2366
|
+
const ext = path7.extname(filePath);
|
|
2367
|
+
if ([".ts", ".tsx"].includes(ext)) {
|
|
2368
|
+
tui.log.info("\u{1F50D} Validating TypeScript...");
|
|
2369
|
+
const validation = await validateTypeScript(filePath);
|
|
2370
|
+
if (!validation.valid) {
|
|
2371
|
+
tui.log.error("\u274C TypeScript validation failed");
|
|
2372
|
+
executionResults += `
|
|
2234
2373
|
[TYPESCRIPT VALIDATION FAILED]:
|
|
2235
2374
|
${validation.error}
|
|
2236
2375
|
|
|
2237
2376
|
`;
|
|
2238
|
-
|
|
2377
|
+
executionResults += `CRITICAL: Fix these errors before proceeding to next task.
|
|
2239
2378
|
`;
|
|
2240
|
-
|
|
2241
|
-
|
|
2379
|
+
} else {
|
|
2380
|
+
tui.log.success("\u2705 TypeScript OK");
|
|
2381
|
+
}
|
|
2242
2382
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
executionResults += `
|
|
2383
|
+
if (ext === ".html") {
|
|
2384
|
+
tui.log.info("\u{1F50D} Validating HTML...");
|
|
2385
|
+
const validation = validateHtmlTagBalance(filePath);
|
|
2386
|
+
if (!validation.valid) {
|
|
2387
|
+
tui.log.error("\u274C HTML validation failed");
|
|
2388
|
+
executionResults += `
|
|
2250
2389
|
[HTML VALIDATION FAILED]:
|
|
2251
2390
|
${validation.error}
|
|
2252
2391
|
|
|
2253
2392
|
`;
|
|
2254
|
-
|
|
2393
|
+
executionResults += `CRITICAL: Fix these errors before proceeding to next task.
|
|
2255
2394
|
`;
|
|
2256
|
-
|
|
2257
|
-
|
|
2395
|
+
} else {
|
|
2396
|
+
tui.log.success("\u2705 HTML OK");
|
|
2397
|
+
}
|
|
2258
2398
|
}
|
|
2259
2399
|
}
|
|
2260
2400
|
} else if (!action.line_range && !action.target_content) {
|
|
@@ -2303,7 +2443,7 @@ ${result}
|
|
|
2303
2443
|
|
|
2304
2444
|
`;
|
|
2305
2445
|
if (targetPath.endsWith("tech-spec.md")) specUpdated = true;
|
|
2306
|
-
const ext =
|
|
2446
|
+
const ext = path7.extname(targetPath);
|
|
2307
2447
|
if ([".ts", ".tsx"].includes(ext)) {
|
|
2308
2448
|
const validation = await validateTypeScript(targetPath);
|
|
2309
2449
|
if (!validation.valid) {
|
|
@@ -2441,7 +2581,7 @@ import { Command as Command4 } from "commander";
|
|
|
2441
2581
|
|
|
2442
2582
|
// src/core/agents/qa-agent.ts
|
|
2443
2583
|
import fs7 from "fs";
|
|
2444
|
-
import
|
|
2584
|
+
import path8 from "path";
|
|
2445
2585
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2446
2586
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2447
2587
|
var AGENT_TYPE5 = "qa_agent";
|
|
@@ -2510,7 +2650,7 @@ async function runQAAgent(options) {
|
|
|
2510
2650
|
}
|
|
2511
2651
|
let projectContext = "";
|
|
2512
2652
|
try {
|
|
2513
|
-
const contextPath =
|
|
2653
|
+
const contextPath = path8.join(process.cwd(), "_sharkrc", "project-context.md");
|
|
2514
2654
|
if (fs7.existsSync(contextPath)) {
|
|
2515
2655
|
projectContext = fs7.readFileSync(contextPath, "utf-8");
|
|
2516
2656
|
tui.log.info(`\u{1F4D8} Context loaded from: _sharkrc/project-context.md`);
|
|
@@ -2621,7 +2761,7 @@ ${projectContext}
|
|
|
2621
2761
|
break;
|
|
2622
2762
|
case "create_file":
|
|
2623
2763
|
if (action.path && action.content) {
|
|
2624
|
-
const fullPath =
|
|
2764
|
+
const fullPath = path8.resolve(process.cwd(), action.path);
|
|
2625
2765
|
const BOM = "\uFEFF";
|
|
2626
2766
|
const contentToWrite = action.content;
|
|
2627
2767
|
const finalContent = contentToWrite.startsWith(BOM) ? contentToWrite : BOM + contentToWrite;
|